【问题标题】:Limitations of the dynamic type in C#C#中动态类型的限制
【发布时间】:2011-04-05 01:48:03
【问题描述】:

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

虽然可以做很多事情 使用动态关键字定义, 有一些关于 它的用法。虽然他们没有显示 塞子,不知道动态数据 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 to Objects 和其他中使用 LINQ 技术:

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

提前致谢。

【问题讨论】:

  • 很明显,动态对象没有名为Method 的属性,也不是IEnumarable。我不认为你提到的那些东西是限制,也许你和作者不明白dynamic 做了什么。
  • 我不这么认为。编译器不会检查成员的有效性(如“方法”)。

标签: c# dynamic-keyword


【解决方案1】:

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

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

d.M(123)

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

如果是这样会怎样

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

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

假设我们想要实现这个功能:我们必须实现什么?首先,我们需要一种表示未绑定的 lambda 的方法。表达式树在设计上仅用于表示所有类型和方法都已知的 lambdas。我们需要发明一种新的“无类型”表达式树。然后我们需要在运行时绑定器中实现 所有 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 是编译时类型 dynamic 和运行时类型 C。运行时 binder 必须做什么?

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

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

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

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

【讨论】:

  • 非常感谢您的详细解答。您能否解释一下“考虑最后一点。Lambda 可以包含语句。实现此功能需要运行时绑定器包含 C# 中每个可能语句的整个语义分析器。”清楚一点?
【解决方案2】:

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

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

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

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

【讨论】:

  • 谢谢。运行时可以决定参数是委托还是表达式?主要原因是实现这些功能会很昂贵?顺便说一句,你这么年轻,还写了一本书,真是令人印象深刻!
  • 编译器需要知道参数是委托还是表达式的事实(以便它可以适当地编译代码)。原则上,编译器可以同时处理动态类型(然后运行时可以决定),但我想这将是太多的工作......
【解决方案3】:

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

这个 C# 语句

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

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

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

相比之下,您将特定的委托类型放在那里,

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

卡扎姆!事情变得简单了。无论 lambda 中包含什么代码,我们现在都可以准确地知道它的类型,这意味着我们可以像编译任何方法体一样编译 IL(例如,我们现在知道 Console.WriteLine 的许多重载中的哪一个)重新调用)。并且该代码具有一种特定类型 (Action&lt;int&gt;),这意味着运行时绑定程序很容易查看 a 是否具有采用该类型参数的 Method

在 C# 中,裸露的 lambda 几乎没有意义。 C# lambda 需要静态上下文来赋予它们意义并排除由许多可能的强制和重载引起的歧义。一个典型的程序很容易提供这个上下文,但是dynamic 的情况下缺少这个重要的上下文。

【讨论】:

  • 我正在重新阅读我的答案,我意识到Console.WriteLine(arg) 正文可能不是一个很好的例子,说明“IL 会是什么样子?”一个棘手的问题,因为我认为它很简单——当arg 的类型为dynamic 时,它看起来像IL。因此,埃里克的回答充满了真正困难的更好例子。我想。我也可能是错的。 :) 不过,我觉得我的回答传达了它的要点,所以我会保持原样。
猜你喜欢
  • 1970-01-01
  • 2022-06-14
  • 2011-10-16
  • 2021-12-02
  • 2010-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-10
相关资源
最近更新 更多