19

我刚刚遇到了最奇怪的事情,我有点介意=此刻被吹了......

下面的程序编译得很好,但是当你运行它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 程序员,我完全被可见性很重要的事实所愚弄,我的意思是毕竟,我访问这个成员就像它是公共接口类型(我认为动态只是反射的语法糖),但动态绑定器不能做出这样的假设,除非你给它一个提示,使用一个简单的演员。

4

2 回答 2

15

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

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

该程序将失败,因为Empty无法访问。dynamic不允许您进行一开始就非法的分析。

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

    dynamic num0 = ((System.Object)container).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 的小副本,我很乐意将它传递给我以前的同事。

于 2013-03-11T14:53:32.763 回答
2

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

这应该(当然)工作:

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

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

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

于 2013-03-11T12:57:44.623 回答