C#中动态类型的限制

你能给我一些C#中动态类型限制的原因吗? 我在“Pro C#2010和.NET 4平台”中读到了它们。 这是一段摘录(如果在这里引用书籍是非法的,请告诉我,我将删除摘录):

虽然可以使用dynamic关键字定义很多东西,但是它的使用存在一些限制。 虽然它们不是show stoppers,但要知道动态数据项在调用方法时不能使用lambda表达式或C#匿名方法。 例如,以下代码将始终导致错误,即使目标方法确实采用了一个带有字符串值并返回void的委托参数。

dynamic a = GetDynamicObject(); // Error! Methods on dynamic data can't use lambdas! a.Method(arg => Console.WriteLine(arg)); 

要绕过此限制,您需要使用第11章(匿名方法和lambda表达式等)中描述的技术直接使用底层委托。 另一个限制是动态数据点无法理解任何扩展方法(参见第12章)。 不幸的是,这还包括来自LINQ API的任何扩展方法。 因此,使用dynamic关键字声明的变量在LINQ to Objects和其他LINQ技术中的使用非常有限:

 dynamic a = GetDynamicObject(); // Error! Dynamic data can't find the Select() extension method! var data = from d in a select d; 

提前致谢。

托马斯的推测非常好。 他对推广方法的推理是现场的。 基本上,为了使扩展方法起作用,我们需要调用站点在运行时以某种方式知道在编译时使用的指令是什么 。 我们根本没有足够的时间或预算来开发一个系统,可以将这些信息保存到呼叫站点。

对于lambda,情况实际上比确定lambda是表达树还是委托的简单问题更复杂。 考虑以下:

 dM(123) 

其中d是动态类型的表达式。 *什么对象应该在运行时作为调用站点“M”的参数传递? 很明显,我们选择了123并通过了。 然后运行时绑定程序中的重载解析算法查看d的运行时类型和int 123的编译时类型,并使用它。

现在怎么样呢

 dM(x=>x.Foo()) 

我们应该将什么对象作为参数传递? 我们无法表示“一个变量的lambda方法,它调用一个名为Foo的未知函数,无论x的类型是什么”。

假设我们想要实现这个function:我们需要实现什么? 首先,我们需要一种方法来表示一个未绑定的lambda 。 表达树仅用于表示所有类型和方法已知的lambda 。 我们需要发明一种新的“无类型”表达式树。 然后我们需要在运行时绑定器中实现lambda绑定的所有规则。

考虑最后一点。 Lambdas可以包含语句实现此function要求运行时绑定程序包含C#中每个可能语句的整个语义分析器

这是我们预算中的数量级。 如果我们想要实现该function,我们今天仍然会在C#4上工作。

不幸的是,这意味着LINQ在动态方面不能很好地工作,因为LINQ当然会在所有地方使用无类型的lambda。 希望在一些假设的未来版本的C#中,我们将拥有一个function更全面的运行时绑定器,并能够对未绑定的lambda进行同色表示。 但如果我是你,我不会屏住呼吸。

更新:评论要求澄清关于语义分析器的观点。

考虑以下重载:

 class C { public void M(Func f) { ... } public void M(Func f) { ... } ... } 

和一个电话

 dM(x=> { using(x) { return 123; } }); 

假设d是编译时类型动态和运行时类型C.运行时绑定器必须做什么?

运行时绑定程序必须在运行时确定表达式x=>{...}是否可以转换为M的每个重载中的每个委托类型。

为此,运行时绑定程序必须能够确定第二个重载不适用。 如果它适用,那么你可以使用int作为using语句的参数,但using语句的参数必须是一次性的。 这意味着运行时绑定程序必须知道using语句的所有规则,并能够正确地报告使用using语句是否合法或非法

显然,这不仅限于使用声明。 运行时绑定程序必须知道所有C#的所有规则,以确定给定的语句lambda是否可转换为给定的委托类型。

我们没有时间编写运行时绑定程序,它本质上是一个生成DLR树而不是IL的全新C#编译器 。 通过不允许lambdas,我们只需要编写一个运行时绑定器,它知道如何绑定方法调用,算术表达式和一些其他简单的调用站点。 允许lambdas使得运行时绑定的问题实现,测试和维护的成本要高几十倍或几百倍。

Lambdas :我认为不支持lambdas作为动态对象参数的一个原因是编译器不知道是将lambda编译为委托还是表达式树。

使用lambda时,编译器根据目标参数或变量的类型决定。 当它是Func<...> (或其他委托)时,它将lambda编译为可执行委托。 当目标是Expression<...>它将lambda编译为表达式树。

现在,当你有一个dynamic类型时,你不知道参数是委托还是表达式,所以编译器无法决定做什么!

扩展方法 :我认为这里的原因是在运行时查找扩展方法会非常困难(也许效率也很低)。 首先,运行时需要知道使用什么名称空间。 然后它需要搜索所有已加载程序集中的所有类,过滤那些可访问的(通过名称空间),然后搜索那些扩展方法…

埃里克(和托马斯)说得很好,但这就是我的想法。

这个C#语句

 a.Method(arg => Console.WriteLine(arg)); 

没有很多背景,没有任何意义。 Lambda表达式本身没有类型,而是可以转换为delegate (或Expression )类型。 因此,收集含义的唯一方法是提供一些强制lambda转换为特定委托类型的上下文。 该上下文通常(如本示例中)重载决策; 给定a的类型,以及该类型的可用重载Method (包括扩展成员),我们可以放置一些给出lambda含义的上下文。

如果没有上下文来产生意义,你最终必须捆绑关于lambda的各种信息,以期在运行时以某种方式绑定未知数。 (你可能会产生什么IL?)

与此形成鲜明对比的是,你在那里放了一个特定的委托类型,

 a.Method(new Action(arg => Console.WriteLine(arg))); 

咔嚓! 事情变得简单。 无论lambda中有什么代码,我们现在都知道它具有什么类型,这意味着我们可以像编译任何方法体一样编译IL(我们现在知道,例如, Console.WriteLine的多个重载中的哪一个我们’重新打电话)。 并且该代码具有一种特定类型( Action ),这意味着运行时绑定器很容易查看是否有一个采用该类型参数的Method

在C#中,裸体lambda几乎毫无意义。 C#lambdas需要静态上下文来赋予它们意义,并排除由许多可能的强制性和过载引起的模糊性。 典型的程序很容易提供这种上下文,但dynamic案例缺乏这种重要的背景。