简体   繁体   English

C#中动态类型的限制

[英]Limitations of the dynamic type in C#

Could you give me some reasons for limitations of the dynamic type in C#? 你能否告诉我一些C#中动态类型限制的原因? I read about them in "Pro C# 2010 and the .NET 4 platform". 我在“Pro C#2010和.NET 4平台”中读到了它们。 Here is an excerpt (if quoting books is illegal here, tell me and I will remove the excerpt): 这是摘录(如果引用书籍在这里是非法的,请告诉我,我将删除摘录):

While a great many things can be defined using the dynamic keyword, there are some limitations regarding its usage. 虽然可以使用dynamic关键字定义很多东西,但是它的使用存在一些限制。 While they are not show stoppers, do know that a dynamic data item cannot make use of lambda expressions or C# anonymous methods when calling a method. 虽然它们不是show stoppers,但要知道动态数据项在调用方法时不能使用lambda表达式或C#匿名方法。 For example, the following code will always result in errors, even if the target method does indeed take a delegate parameter which takes a string value and returns void. 例如,以下代码将始终导致错误,即使目标方法确实采用了一个带有字符串值并返回void的委托参数。

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

To circumvent this restriction, you will need to work with the underlying delegate directly, using the techniques described in Chapter 11 (anonymous methods and lambda expressions, etc). 要绕过此限制,您需要使用第11章(匿名方法和lambda表达式等)中描述的技术直接使用底层委托。 Another limitation is that a dynamic point of data cannot understand any extension methods (see Chapter 12). 另一个限制是动态数据点无法理解任何扩展方法(参见第12章)。 Unfortunately, this would also include any of the extension methods which come from the LINQ APIs. 不幸的是,这还包括来自LINQ API的任何扩展方法。 Therefore, a variable declared with the dynamic keyword has very limited use within LINQ to Objects and other LINQ technologies: 因此,使用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; 

Thanks in advance. 提前致谢。

Tomas's conjectures are pretty good. 托马斯的推测非常好。 His reasoning on extension methods is spot on. 他对推广方法的推理是现场的。 Basically, to make extension methods work we need the call site to at runtime somehow know what using directives were in force at compile time . 基本上,为了使扩展方法起作用,我们需要调用站点在运行时以某种方式知道在编译时使用指令是什么 We simply did not have enough time or budget to develop a system whereby this information could be persisted into the call site. 我们根本没有足够的时间或预算来开发一个系统,可以将这些信息保存到呼叫站点。

For lambdas, the situation is actually more complex than the simple problem of determining whether the lambda is going to expression tree or delegate. 对于lambda,情况实际上比确定lambda是表达树还是委托的简单问题更复杂。 Consider the following: 考虑以下:

d.M(123)

where d is an expression of type dynamic. 其中d是动态类型的表达式。 *What object should get passed at runtime as the argument to the call site "M"? *什么对象应该在运行时作为调用站点“M”的参数传递? Clearly we box 123 and pass that. 很明显,我们选择了123并通过了。 Then the overload resolution algorithm in the runtime binder looks at the runtime type of d and the compile-time type of the int 123 and works with that. 然后运行时绑定程序中的重载解析算法查看d的运行时类型和int 123的编译时类型并使用它。

Now what if it was 现在怎么样呢

d.M(x=>x.Foo())

Now what object should we pass as the argument? 我们应该将什么对象作为参数传递? We have no way to represent "lambda method of one variable that calls an unknown function called Foo on whatever the type of x turns out to be". 我们无法表示“一个变量的lambda方法,它调用一个名为Foo的未知函数,无论x的类型是什么”。

Suppose we wanted to implement this feature: what would we have to implement? 假设我们想要实现这个功能:我们要实现什么? First, we'd need a way to represent an unbound lambda . 首先,我们需要一种方法来表示一个未绑定的lambda Expression trees are by design only for representing lambdas where all types and methods are known . 表达树仅用于表示已知所有类型和方法的lambda We'd need to invent a new kind of "untyped" expression tree. 我们需要发明一种新的“无类型”表达式树。 And then we'd need to implement all of the rules for lambda binding in the runtime binder. 然后我们需要在运行时绑定器中实现lambda绑定的所有规则。

Consider that last point. 考虑最后一点。 Lambdas can contain statements . Lambdas可以包含语句 Implementing this feature requires that the runtime binder contain the entire semantic analyzer for every possible statement in C# . 实现此功能要求运行时绑定程序包含C#中每个可能语句的整个语义分析器

That was orders of magnitude out of our budget. 这是我们预算中的数量级。 We'd still be working on C# 4 today if we'd wanted to implement that feature. 如果我们想要实现该功能,我们今天仍然会在C#4上工作。

Unfortunately this means that LINQ doesn't work very well with dynamic, because LINQ of course uses untyped lambdas all over the place. 不幸的是,这意味着LINQ在动态方面不能很好地工作,因为LINQ当然会在所有地方使用无类型的lambda。 Hopefully in some hypothetical future version of C# we will have a more fully-featured runtime binder and the ability to do homoiconic representations of unbound lambdas. 希望在一些假设的未来版本的C#中,我们将拥有一个功能更全面的运行时绑定器,并能够对未绑定的lambda进行同色表示。 But I wouldn't hold my breath waiting if I were you. 但如果我是你,我不会屏住呼吸。

UPDATE: A comment asks for clarification on the point about the semantic analyzer. 更新:评论要求澄清关于语义分析器的观点。

Consider the following overloads: 考虑以下重载:

class C {
  public void M(Func<IDisposable, int> f) { ... }
  public void M(Func<int, int> f) { ... }
  ...
}

and a call 和一个电话

d.M(x=> { using(x) { return 123; } });

Suppose d is of compile time type dynamic and runtime type C. What must the runtime binder do? 假设d是编译时类型动态和运行时类型C.运行时绑定器必须做什么?

The runtime binder must determine at runtime whether the expression x=>{...} is convertible to each of the delegate types in each of the overloads of M. 运行时绑定程序必须在运行时确定表达式x=>{...}是否可以转换为M的每个重载中的每个委托类型。

In order to do that, the runtime binder must be able to determine that the second overload is not applicable. 为此,运行时绑定程序必须能够确定第二个重载不适用。 If it were applicable then you could have an int as the argument to a using statement, but the argument to a using statement must be disposable. 如果它适用,那么你可以使用int作为using语句的参数,但using语句的参数必须是一次性的。 That means that the runtime binder must know all the rules for the using statement and be able to correctly report whether any possible use of the using statement is legal or illegal . 这意味着运行时绑定程序必须知道using语句的所有规则,并能够正确地报告对using语句的任何可能使用是合法还是非法

Clearly that is not restricted to the using statement. 显然,这不仅限于使用声明。 The runtime binder must know all the rules for all of C# in order to determine whether a given statement lambda is convertible to a given delegate type. 运行时绑定程序必须知道所有C#的所有规则,以确定给定的语句lambda是否可转换为给定的委托类型。

We did not have time to write a runtime binder that was essentially an entire new C# compiler that generates DLR trees rather than IL . 我们没有时间编写一个运行时绑定程序,它本质上是一个生成DLR树而不是IL的全新C#编译器 By not allowing lambdas we only have to write a runtime binder that knows how to bind method calls, arithmetic expressions and a few other simple kinds of call sites. 通过不允许lambdas,我们只需要编写一个运行时绑定器,它知道如何绑定方法调用,算术表达式和一些其他简单的调用站点。 Allowing lambdas makes the problem of runtime binding on the order of dozens or hundreds of times more expensive to implement, test and maintain. 允许lambda使得运行时绑定的问题实现,测试和维护的成本要高几十倍或几百倍。

Lambdas : I think that one reason for not supporting lambdas as parameters to dynamic objects is that the compiler wouldn't know whether to compile the lambda as a delegate or as an expression tree. Lambdas :我认为不支持lambdas作为动态对象参数的一个原因是编译器不知道是将lambda编译为委托还是表达式树。

When you use a lambda, the compiler decides based on the type of the target parameter or variable. 使用lambda时,编译器根据目标参数或变量的类型决定。 When it is Func<...> (or other delegate) it compiles the lambda as an executable delegate. 当它是Func<...> (或其他委托)时,它将lambda编译为可执行委托。 When the target is Expression<...> it compiles lambda into an expression tree. 当目标是Expression<...>它将lambda编译为表达式树。

Now, when you have a dynamic type, you don't know whether the parameter is delegate or expression, so the compiler cannot decide what to do! 现在,当你有一个dynamic类型时,你不知道参数是委托还是表达式,所以编译器无法决定做什么!

Extension methods : I think that the reason here is that finding extension methods at runtime would be quite difficult (and perhaps also inefficient). 扩展方法 :我认为这里的原因是在运行时查找扩展方法将非常困难(并且可能也是低效的)。 First of all, the runtime would need to know what namespaces were referenced using using . 首先,运行时需要知道什么名称空间使用参考using Then it would need to search all classes in all loaded assemblies, filter those that are accessible (by namespace) and then search those for extension methods... 然后,它需要搜索所有已加载程序集中的所有类,过滤那些可访问的(通过命名空间),然后搜索那些扩展方法...

Eric (and Tomas) says it well, but here is how I think of it. 埃里克(和托马斯)说得很好,但这就是我的想法。

This C# statement 这个C#语句

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

has no meaning without a lot of context. 没有很多背景,没有意义。 Lambda expressions themselves have no types, rather they are convertible to delegate (or Expression ) types. Lambda表达式本身没有类型,而是可以转换为delegate (或Expression )类型。 So the only way to gather the meaning is to provide some context which forces the lambda to be converted to a specific delegate type. 因此,收集含义的唯一方法是提供一些强制lambda转换为特定委托类型的上下文。 That context is typically (as in this example) overload resolution; 该上下文通常(如本示例中)重载决策; given the type of a , and the available overloads Method on that type (including extension members), we can possibly place some context that gives the lambda meaning. 给定的类型a ,以及可用的重载Method上该类型(包括扩展名成员),我们都不可能把一些背景,让拉姆达意义。

Without that context to produce the meaning, you end up having to bundle up all kinds of information about the lambda in the hopes of somehow binding the unknowns at runtime. 如果没有上下文来产生意义,你最终必须捆绑关于lambda的各种信息,以期在运行时以某种方式绑定未知数。 (What IL could you possibly generate?) (你可能会产生什么IL?)

In vast contrast, one you put a specific delegate type there, 与此形成鲜明对比的是,你在那里放了一个特定的委托类型,

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

Kazam! 咔嚓! Things just got easy. 事情变得容易了。 No matter what code is inside the lambda, we now know exactly what type it has, which means we can compile IL just as we would any method body (we now know, for example, which of the many overloads of Console.WriteLine we're calling). 无论lambda中有什么代码,我们现在都知道它具有什么类型,这意味着我们可以像编译任何方法体一样编译IL(我们现在知道,例如, Console.WriteLine的多个重载中的哪一个我们'重新打电话)。 And that code has one specific type ( Action<int> ), which means it is easy for the runtime binder to see if a has a Method that takes that type of argument. 和该代码具有一个特定类型( Action<int> ),这意味着它易于运行时粘合剂,以查看是否a具有Method即采用该类型的参数。

In C#, a naked lambda is almost meaningless. 在C#中,裸体lambda几乎毫无意义。 C# lambdas need static context to give them meaning and rule out ambiguities that arise from many possible coercisons and overloads. C#lambdas需要静态上下文来赋予它们意义,并排除由许多可能的强制和过载引起的模糊性。 A typical program provides this context with ease, but the dynamic case lacks this important context. 典型的程序很容易提供这种上下文,但dynamic案例缺乏这种重要的背景。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM