C#中动态类型的限制

时间:2010-08-28 00:40:29

标签: c# dynamic-keyword

你能否告诉我一些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;

提前致谢。

3 个答案:

答案 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)

Eric(和Tomas)说得很好,但这就是我的想法。

这个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案例缺乏这个重要的背景。