C#动态类型问题

我只是遇到了最奇怪的事情,我有点心思=此刻…

下面的程序编译得很好,但是当你运行它时,当你尝试读取Value时会得到一个RuntimeBinderException'object' does not contain a definition for 'Value'

 class Program { interface IContainer { int Value { get; } } class Factory { class Empty : IContainer { public int Value { get { return 0; } } } static IContainer nullObj = new Empty(); public IContainer GetContainer() { return nullObj; } } static void Main(string[] args) { dynamic factory = new Factory(); dynamic container = factory.GetContainer(); var num0 = container.Value; // WTF!? RuntimeBinderException, really? } } 

这是令人兴奋的部分。 在Factory类之外移动嵌套类型Factory+Empty ,如下所示:

 class Empty : IContainer { public int Value { get { return 0; } } } class Factory... 

程序运行得很好,有人在乎解释为什么会这样吗?

编辑

在我的编码冒险中,我当然做了一些我应该首先考虑的事情。 这就是为什么你看到我在私人和内部阶级之间的差异。 这是因为我设置了InternalsVisibleToAttribute ,这使我的测试项目(在这个例子中消耗了比特)表现得像他们一样,这完全是设计的,尽管从一开始就暗指我。

阅读Eric Lippert的答案,对其余部分进行了很好的解释。

真正引起我注意的是动态绑定器考虑了实例类型的可见性。 我有很多JavaScript经验,作为一个JavaScript程序员,真的没有公共或私人这样的东西,我完全被可见性很重要的事实所迷惑,我的意思是,毕竟,我正在访问这个成员,好像它是公共接口类型(我认为动态只是用于reflection的语法糖)但动态绑定器不能做出这样的假设,除非你使用简单的强制转换给它一个提示。

C#中“动态”的基本原则是:在运行时对表达式进行类型分析, 就好像运行时类型是编译时类型一样 。 那么让我们看看如果我们真的这样做会发生什么:

  dynamic num0 = ((Program.Factory.Empty)container).Value; 

该程序将失败,因为无法访问Emptydynamic不允许你做一个本来就是非法的分析。

但是,运行时分析器意识到这一点,并决定作弊。 它问自己“是否有一个可以访问的基类?” 答案显然是肯定的。 所以它决定回到基类并分析:

  dynamic num0 = ((System.Object)container).Value; 

哪个失败是因为该程序会给你一个“对象没有名为Value的成员”错误。 你得到的错误是哪个。

动态分析永远不会说“哦,你一定有意”

  dynamic num0 = ((Program.IContainer)container).Value; 

因为当然, 如果这就是你的意思,那就是你本来应该写的东西 。 同样, dynamic的目的是回答问题,如果编译器知道运行时类型会发生什么 ,并且转换为接口不会为您提供运行时类型。

当你将Empty移到外面时,动态运行时分析器假装你写了:

  dynamic num0 = ((Empty)container).Value; 

现在可以访问Empty并且演员表是合法的,因此您可以获得预期的结果。


更新:

可以将该代码编译成一个程序集,引用这个程序集,如果Empty类型在类之外,它将在默认情况下使其成为内部

我无法重现所描述的行为。 让我们试试一个例子:

 public class Factory { public static Thing Create() { return new InternalThing(); } } public abstract class Thing {} internal class InternalThing : Thing { public int Value {get; set;} } 

 > csc /t:library bar.cs 

 class P { static void Main () { System.Console.WriteLine(((dynamic)(Factory.Create())).Value); } } 

 > csc foo.cs /r:bar.dll > foo Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'Thing' does not contain a definition for 'Value' 

并且您看到它是如何工作的:运行时绑定程序检测到InternalThing是外部程序集的内部,因此在foo.exe中无法访问。 所以它回归到公共基础类型Thing ,它是可访问的,但没有必要的属性。

我无法重现您描述的行为,如果您可以重现它,那么您就发现了一个错误。 如果你有一个小错误的bug,我很乐意将它传递给我以前的同事。

我想,在运行时,容器方法调用只是在私有的Empty类中解决,这会导致代码失败。 据我所知,动态不能用于访问私人成员(或私人类的公共成员)

这应该(当然)工作:

 var num0 = ((IContainer)container).Value; 

这里是类Empty,它是私有的:所以你不能在声明类(工厂)之外操作Empty实例。 这就是你的代码失败的原因。

如果Empty是内部的,你可以在整个程序集中操纵它的实例(好吧,不是因为Factory是私有的),允许所有动态调用,并且你的代码可以工作。