C#4会允许"动态转换"吗?如果不允许,那么C#应该支持吗?

6
我不是指将一个低层接口或基类转换为更高级别的派生类,而是指采用我创建的接口定义,然后动态地将不从该接口派生但支持所有调用的不同对象转换为该接口。
例如,
interface IMyInterface
{
   bool Visible
   {
      get;
   }
}

TextBox myTextBox = new TextBox();
IMyInterface i = (dynamic<IMyInterface>)myTextBox;

对于已知类型,可以在编译时实现此操作,对于使用dynamic声明的实例,则可以在运行时实现。接口定义已知,类型也已知(在此示例中),因此编译器可以确定对象是否支持由接口定义的调用,并为我们执行一些魔法以进行转换。
我猜想C#4不支持这个(我找不到相关的参考资料),但我想确切地知道。如果它不支持,我想讨论它是否应该包含在将来的语言版本中,以及支持和反对的理由。对我来说,这似乎是一个很好的补充,可以在不必创建全新的类型来包装现有框架类型的情况下实现更大的多态性。
更新 以防有人指责我剽窃,我不知道Jon Skeet已经提出了这个建议。然而,很高兴知道我们想到了非常相似的语法,这表明它可能至少是直观的。同时,“拥有原创性的想法”仍然是我的待办事项之一。

1
这岂不是本质上的结构化类型(http://en.wikipedia.org/wiki/Structural_typing)吗? - Noldorin
@Noldorin:谢谢,我不知道它有一个名字。浏览一下,我会说是的。 - Jeff Yates
1
@Jeff:是的,我现在非常有信心它们是同一件事情,尽管Jon Skeet博客文章中的一些评论将其描述为鸭子类型(这与结构类型略有不同)。无论它是什么,这是一个非常好的想法……或许在C# 5中可以实现。 :) - Noldorin
4个回答

3
我认为这是有问题的。您正在引入两个未耦合的类之间的耦合。
考虑以下代码。
public interface IFoo
{
   int MethodA();
   int MethodB();
}

public class Bar
{
   int MethodA();
   int MethodB();
}

public class SomeClass
{
   int MethodFoo(IFoo someFoo);
}

这样做是否合法?
int blah = someClass.MethodFoo((dynamic<IFoo>)bar);

看起来这应该是合法的,因为编译器应该能够将bar动态地类型化为实现IFoo的某个东西。

然而,在这一点上,您通过代码中完全不同的部分的调用将IFoo和Bar耦合在一起。

如果您编辑Bar,因为它不再需要MethodB,突然someClass.MethodFood就不能工作了,即使Bar和IFoo没有关系。

同样,如果您向IFoo添加MethodC(),您的代码将再次中断,即使IFoo和Bar表面上没有关系。

事实是,尽管在您无法控制的对象之间存在相似性的选择情况下,这将是有用的,但接口必须明确附加到对象,并且原因是编译器可以保证对象实现它。


1
但是OP明确表示将其标记为dynamic。如果这破坏了一致性,那么dynamic o = new object(); o.InvalidMethod();也应该这样做。 - Mehrdad Afshari
你提供的例子不正确,因为你没有明确请求动态类型。我明确请求动态类型,如果两种类型在编译时都已知,编译器可以使用它来标记类型不匹配错误;如果在运行时,DLR 可以确定调用是否存在,就像它必须为 C# 4 的现有动态类型机制中已经调用的单个调用执行的那样。 - Jeff Yates
我不确定,我坚决反对有这样的选项,但我反对使用它。在我看来,明确声明你的耦合(通过声明“Bar:IFoo”)更清晰。 如果没有调用Bar.MethodB(),那么我应该能够删除MethodB()。Bar没有实现接口,因此它不必实现该方法,并且从未调用MethodB,因此它不应该破坏代码。但是我们会发现,在删除它之后,Bar被绑定实现它,只是它被耦合到执行代码中的接口而不是类定义代码中。 - DevinB
我认为你(程序员)通过声明某个对象将始终实现某个接口,正在承担很大的风险。当你动态地说出someobject.MethodDoesntExistYet()时,你正在对该单个对象做出承诺。当你动态地将其绑定到一个接口时,你正在对对象和接口都做出承诺。但我理解你的观点。 - DevinB
1
我理解其中的风险,但这就是动态后期绑定所承担的风险。假设我们已经有了这个,动态请求特定集合的方法对于类型而言具有一定的价值。如果运行时告诉你该类型不支持该接口的方法,你可以采取相应的措施,就像你请求单个方法时一样。 - Jeff Yates
显示剩余5条评论

3

2
该死。总有一天我会有一个原创的想法。:( - Jeff Yates

1

开源框架Impromptu-Interface使用C# 4和dlr实现此功能。

using ImpromptuInterface;

interface IMyInterface
{
   bool Visible
   {
      get;
   }
}

TextBox myTextBox = new TextBox();
IMyInterface i = myTextBox.ActLike<IMyInterface>();

由于它使用dlr,因此它也适用于ExpandoObject和DynamicObject。


这与执行强制转换有何不同?我的原始建议是开发便利性,不会对运行时产生任何影响。 - Jeff Yates
这就是我的观点,它与现有的运行时稍有不同,只是用了略微不同的语法。它的运行时成本很小,生成的代理非常轻量级,如果仅看显式调用成本,则为1个静态调用+1个DLR调用。 - jbtule

1

没有必要让C#支持这个,因为它可以作为库非常干净地实现。

我看过三到四个独立的实现(在找到它们之前我自己也开始写了一个)。这是我见过的最全面的处理方式:

http://bartdesmet.net/blogs/bart/archive/2008/11/10/introducing-the-c-ducktaper-bridging-the-dynamic-world-with-the-static-world.aspx

一旦 DLR 集成到运行时中,实现可能会更容易。

由于给定接口的包装器/转发器类可以生成一次然后缓存,因此可以对调用站点等进行缓存,这样性能应该非常好。

相比之下,我认为语言特性的 dynamic 关键字非常复杂、不必要且可能灾难性地偏离了以前具有明显静态类型哲学并为其未来改进提供了明显方向的语言。他们应该坚持这一点,并使类型推断的工作变得越来越好,直到类型变得更加不可见。他们可以在许多领域发展语言而不破坏现有程序,但由于资源限制(例如无法在更多地方使用 var 的原因是他们需要重写编译器,而他们没有时间),他们没有这样做。

C# 4.0 中仍有一些有用的功能(例如泛型变体特性),但是在使类型系统更智能、更自动化、更强大以便在编译时检测问题方面还有很多其他事情可以做。相反,我们基本上只是得到了一个噱头。


我认为在考虑到现在的互操作性问题时,包含动态关键字的动机是可以接受的。通过使用dynamic关键字,它保持了语言的静态类型特性,并迫使开发人员思考他们正在编写的内容,并隐式地使用动态类型 - 而不是例如使用对象关键字进行动态类型对象。我认为将类型化变成看不见的状态正是你所不希望在静态类型语言中出现的情况。(在多个位置使用var不仅仅是编译器重写,而且可能是语言语义... - Jeff Yates
...更改时,您必须包括有关谁首先分配变量的规则(以便可以正确推断类型)。我肯定不认为动态是一种噱头-对于诸如COM互操作之类的事情,它是向前迈出的巨大一步,并且是在静态类型语言中提供动态语言支持的好方法。编译器在处理类型使用方面做了大量工作,但我不确定做更多是否是一个好主意,因为它可能导致懒惰开发糟糕的代码。编译器应该强制执行规则,而不是给我们忽略它们的方式。 - Jeff Yates
这是在静态类型语言中提供动态语言支持的一种方式;在我看来(已经解释过了),这是错误的方式。关于强制规则,我只谈论像 Haskell 这样的语言所做的推断类型。规则是被强制执行的,因此不能被忽视。然而,需要重复说相同的事情的需求被减少了。这肯定是一件好事。 - Daniel Earwicker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接