C#动态类型问题

时间:2013-03-11 12:20:04

标签: c# dynamic types

我刚遇到最奇怪的事情,此刻我有点 mind = blown ......

以下程序编译正常,但是当您运行它时,当您尝试阅读RuntimeBinderException时,会得到Value'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+Empty移到Factory类之外,如下所示:

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

class Factory...

程序运行得很好,有人在乎解释原因吗?

修改

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

阅读Eric Lippert的答案,以便对其余部分做出很好的解释。

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

2 个答案:

答案 0 :(得分:15)

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,我很乐意将它传递给我以前的同事。

答案 1 :(得分:2)

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

这应该(当然)有效:

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

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

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