C# 中动态类型的限制

发布于 2024-09-16 11:24:35 字数 798 浏览 0 评论 0原文

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

虽然很多事情都可以 使用dynamic关键字定义, 有一些限制 它的用法。虽然他们没有表现出来 塞子,确实知道动态数据 item 无法使用 lambda 表达式或 C# 匿名方法 调用方法时。例如, 总是会出现以下代码 错误,即使目标方法 确实需要一个委托参数 它接受一个字符串值并返回 空白。

动态 a = GetDynamicObject(); 
// 错误!动态数据的方法不能使用 lambda! 
a.Method(arg => Console.WriteLine(arg));

为了规避此限制,您 需要与底层一起工作 直接委托,使用 第 11 章中描述的技术 (匿名方法和 lambda 表达式等)。另一个限制 是动态数据点不​​能 了解任何扩展方法(请参阅 第12章)。不幸的是,这会 还包括任何扩展名 来自 LINQ API 的方法。 因此,用以下方式声明的变量 动态关键字的作用非常有限 在 LINQ to Objects 和其他对象中使用 LINQ 技术:

动态 a = GetDynamicObject(); 
// 错误!动态数据找不到Select()扩展方法! 
var data = from d in a select d;

提前致谢。

Could you give me some reasons for limitations of the dynamic type in C#? I read about them in "Pro C# 2010 and the .NET 4 platform". 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. 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. 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.

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). Another limitation
is that a dynamic point of data cannot
understand any extension methods (see
Chapter 12). Unfortunately, this would
also include any of the extension
methods which come from the LINQ APIs.
Therefore, a variable declared with
the dynamic keyword has very limited
use within LINQ to Objects and other
LINQ technologies:

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

Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

千年*琉璃梦 2024-09-23 11:24:35

托马斯的猜想相当不错。他对扩展方法的推理是正确的。基本上,为了使扩展方法起作用,我们需要调用站点在运行时以某种方式知道编译时哪些 using 指令有效。我们只是没有足够的时间或预算来开发一个可以将这些信息持久保存到调用站点中的系统。

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

d.M(123)

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

现在,如果是

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

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

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

考虑最后一点。 Lambda 可以包含语句实现此功能要求运行时绑定器包含 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 是编译时类型动态和运行时类型 C。运行时绑定器必须做什么?

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

。为了做到这一点,运行时绑定器必须能够确定第二个重载不适用。如果它适用,那么您可以将 int 作为 using 语句的参数,但 using 语句的参数必须是一次性的。这意味着运行时绑定器必须了解 using 语句的所有规则,并能够正确报告任何可能的 using 语句的使用是否合法或非法。

显然这不限于 using 语句。运行时绑定器必须了解所有 C# 的所有规则,以便确定给定的语句 lambda 是否可转换为给定的委托类型。

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

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. Consider the following:

d.M(123)

where d is an expression of type dynamic. *What object should get passed at runtime as the argument to the call site "M"? Clearly we box 123 and pass that. 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.

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".

Suppose we wanted to implement this feature: what would we have to implement? First, we'd need a way to represent an unbound lambda. Expression trees are by design only for representing lambdas where all types and methods are known. 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.

Consider that last point. Lambdas can contain statements. Implementing this feature requires that the runtime binder contain the entire semantic analyzer for every possible statement in 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.

Unfortunately this means that LINQ doesn't work very well with dynamic, because LINQ of course uses untyped lambdas all over the place. 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. 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?

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.

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. 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.

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.

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. 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. Allowing lambdas makes the problem of runtime binding on the order of dozens or hundreds of times more expensive to implement, test and maintain.

通知家属抬走 2024-09-23 11:24:35

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

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

现在,当您拥有动态类型时,您不知道参数是委托还是表达式,因此编译器无法决定要做什么!

扩展方法:我认为这里的原因是在运行时查找扩展方法会非常困难(而且可能效率也很低)。首先,运行时需要知道使用 using 引用了哪些命名空间。然后,它需要搜索所有加载的程序集中的所有类,过滤那些可访问的(按命名空间),然后搜索那些扩展方法......

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.

When you use a lambda, the compiler decides based on the type of the target parameter or variable. When it is Func<...> (or other delegate) it compiles the lambda as an executable delegate. When the target is Expression<...> it compiles lambda into an expression tree.

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!

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. 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...

青柠芒果 2024-09-23 11:24:35

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

此 C# 语句

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

如果没有大量上下文, 就没有任何意义。 Lambda 表达式本身没有类型,而是可以转换为委托(或表达式)类型。因此,收集含义的唯一方法是提供一些上下文,强制将 lambda 转换为特定的委托类型。该上下文通常是(如本示例中所示)重载解析;给定 a 的类型以及该类型上可用的重载 Method(包括扩展成员),我们可以放置一些上下文来赋予 lambda 含义。

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

相比之下,您在那里放置了一个特定的委托类型,

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

Kazam!事情变得容易了。无论 lambda 中包含什么代码,我们现在都确切地知道它的类型,这意味着我们可以像编译任何方法体一样编译 IL(例如,我们现在知道 Console.cs 的许多重载中的哪一个)。我们正在调用 WriteLine)。并且该代码具有一种特定类型 (Action),这意味着运行时绑定器可以轻松查看 a 是否具有 Method 采用这种类型的论证。

在 C# 中,裸露的 lambda 几乎毫无意义。 C# lambda 需要静态上下文来赋予它们含义并排除许多可能的强制和重载引起的歧义。典型的程序可以轻松地提供此上下文,但动态情况缺乏此重要的上下文。

Eric (and Tomas) says it well, but here is how I think of it.

This C# statement

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. 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. 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.

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. (What IL could you possibly generate?)

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). 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.

In C#, a naked lambda is almost meaningless. C# lambdas need static context to give them meaning and rule out ambiguities that arise from many possible coercisons and overloads. A typical program provides this context with ease, but the dynamic case lacks this important context.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文