你能否告诉我一些C#中动态类型限制的原因?我在“Pro C#2010和.NET 4平台”中读到了它们。这是摘录(如果在这里引用书籍是非法的,请告诉我,我将删除摘录):
虽然可以做很多事情 使用dynamic关键字定义, 有一些限制 它的用法。虽然他们没有表现出来 塞子,确实知道动态数据 item无法使用lambda 表达式或C#匿名方法 在调用方法时。例如, 始终会得到以下代码 在错误中,即使是目标方法 确实采用了委托参数 它接受一个字符串值并返回 无效。
dynamic a = GetDynamicObject(); // Error! Methods on dynamic data can’t use lambdas! a.Method(arg => Console.WriteLine(arg));
为了规避这个限制,你 将需要与底层证券合作 直接委托,使用 第11章描述的技术 (匿名方法和lambda 表达式等)。另一个限制 是一个动态的数据点不能 了解任何扩展方法(参见 第12章)。不幸的是,这会 还包括任何扩展名 来自LINQ API的方法。 因此,声明了一个变量 动态关键字非常有限 在LINQ中使用对象和其他 LINQ技术:
dynamic a = GetDynamicObject(); // Error! Dynamic data can’t find the Select() extension method! var data = from d in a select d;
提前致谢。
答案 0 :(得分:16)
对于lambdas,情况实际上比确定lambda是表达树还是委托的简单问题更复杂。请考虑以下事项:
d.M(123)
其中d是动态类型的表达式。 *什么对象应该在运行时作为调用站点“M”的参数传递?很明显,我们选择了123并通过了。然后运行时绑定程序中的重载解析算法查看d的运行时类型和int 123的编译时类型,并使用它。
现在怎么样
d.M(x=>x.Foo())
现在我们应该传递什么对象作为参数?我们无法表示“一个变量的lambda方法,它调用一个名为Foo的未知函数,无论x的类型是什么”。
假设我们想要实现此功能:我们必须实现什么?首先,我们需要一种方法来表示未绑定的lambda 。表达式树仅用于表示 lambdas,其中所有类型和方法都是已知的。我们需要发明一种新的“无类型”表达式树。然后我们需要在运行时绑定程序中为lambda绑定实现所有规则。
考虑最后一点。 Lambdas可以包含语句。 实现此功能要求运行时活页夹包含用于 C#中每个可能的语句的整个语义分析器。
这超出了我们预算的数量级。如果我们想要实现该功能,我们今天仍然会在C#4上工作。
不幸的是,这意味着LINQ在动态方面不能很好地工作,因为LINQ当然会在所有地方使用无类型的lambda。希望在一些假设的未来版本的C#中,我们将拥有一个功能更全面的运行时绑定器,并能够对未绑定的lambda进行同色表示。但如果我是你,我不会屏住呼吸。
更新:评论要求澄清关于语义分析器的观点。
考虑以下重载:
class C {
public void M(Func<IDisposable, int> f) { ... }
public void M(Func<int, int> f) { ... }
...
}
和电话
d.M(x=> { using(x) { return 123; } });
假设d是编译时类型dynamic和运行时类型C.运行时绑定器必须做什么?
运行时绑定程序必须在运行时确定 表达式x=>{...}
是否可以转换为M的每个重载中的每个委托类型。
为此,运行时绑定程序必须能够确定第二个重载不适用。如果它适用,那么你可以使用int作为using语句的参数,但using语句的参数必须是一次性的。这意味着运行时绑定程序必须知道using语句的所有规则,并能够正确地报告是否可能使用using语句是合法的还是非法的。
显然,这不仅限于使用声明。运行时绑定程序必须知道所有C#的所有规则,以确定给定语句lambda是否可转换为给定的委托类型。
我们没有时间编写一个运行时绑定程序,它基本上是一个生成DLR树而不是IL 的全新C#编译器。通过不允许lambdas,我们只需要编写一个运行时绑定器,它知道如何绑定方法调用,算术表达式和一些其他简单的调用站点。允许lambda使得运行时绑定的问题实现,测试和维护的成本要高几十倍或几百倍。
答案 1 :(得分:9)
Lambdas :我认为不支持lambdas作为动态对象参数的一个原因是编译器不知道是将lambda编译为委托还是表达式树。
使用lambda时,编译器根据目标参数或变量的类型决定。当它是Func<...>
(或其他委托)时,它将lambda编译为可执行委托。当目标为Expression<...>
时,它将lambda编译为表达式树。
现在,当你有dynamic
类型时,你不知道参数是委托还是表达式,所以编译器无法决定做什么!
扩展方法:我认为这里的原因是在运行时查找扩展方法会非常困难(也许效率也很低)。首先,运行时需要知道使用using
引用了哪些名称空间。然后,它需要搜索所有已加载程序集中的所有类,过滤那些可访问的(通过命名空间),然后搜索那些扩展方法......
答案 2 :(得分:3)
这个C#声明
a.Method(arg => Console.WriteLine(arg));
没有上下文的 lot ,没有意义。 Lambda表达式本身没有类型,而是可以转换为delegate
(或Expression
)类型。因此,收集含义的唯一方法是提供一些强制lambda转换为特定委托类型的上下文。该上下文通常(如本示例中)重载决策;给定a
的类型,以及该类型(包括扩展成员)的可用重载Method
,我们可以放置一些给出lambda含义的上下文。
如果没有这个上下文来产生意义,你最终必须捆绑关于lambda的各种信息,希望以某种方式在运行时绑定未知数。 (你可能会产生什么IL?)
与此形成鲜明对比的是,你在那里放置了一个特定的委托类型,
a.Method(new Action<int>(arg => Console.WriteLine(arg)));
咔嚓!事情变得容易了。无论lambda中有什么代码,我们现在都知道它具有什么类型,这意味着我们可以像编译任何方法体一样编译IL(例如,我们现在知道Console.WriteLine
的许多重载中的哪一个我们正在打电话)。并且该代码具有一种特定类型(Action<int>
),这意味着运行时绑定器很容易查看a
是否具有采用该类型参数的Method
。
在C#中,赤裸裸的lambda几乎毫无意义。 C#lambdas需要静态上下文来赋予它们意义,并排除由许多可能的强制和过载引起的模糊性。一个典型的程序很容易提供这个上下文,但dynamic
案例缺乏这个重要的背景。