简体   繁体   English

C# 动态类型问题

[英]C# dynamic type gotcha

I just ran into the strangest thing and I'm a bit mind = blown at the moment...我刚刚遇到了最奇怪的事情,我有点介意=此刻...

The following program compiles fine but when you run it you get a RuntimeBinderException when you try to read Value .下面的程序编译得很好,但是当你运行它时,当你尝试读取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?
    }
}

Here's the mind blowing part.这是令人兴奋的部分。 Move the nested type Factory+Empty outside of the Factory class, like so:将嵌套类型Factory+Empty Factory类之外,如下所示:

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

class Factory...

And the program runs just fine, anyone care to explain why that is?程序运行得很好,有人愿意解释这是为什么吗?

EDIT编辑

In my adventure of coding I of course did something I should have thought about first.在我的编码冒险中,我当然做了一些我应该首先考虑的事情。 That's why you see me rambling a bit about the difference between class private and internal.这就是为什么你会看到我对 class private 和 internal 之间的区别漫不经心。 This was because I had set the InternalsVisibleToAttribute which made my test project (which was consuming the bits in this instance) behave the way they did, which was all by design, although alluding me from the start.这是因为我设置了InternalsVisibleToAttribute ,它使我的测试项目(在本例中消耗了比特)按照他们的方式运行,这完全是设计的,尽管从一开始就暗指我。

Read Eric Lippert's answer for a good explanation of the rest.阅读 Eric Lippert 的回答,以获得对其余部分的一个很好的解释。

What caught me really of guard was that the dynamic binder takes the visibility of the type of the instance in mind.真正让我感到警惕的是,动态绑定器考虑了实例类型的可见性。 I have a lot of JavaScript experience and as a JavaScript programmer where there really isn't such a thing as public or private, I was completely fooled by the fact that the visibility mattered, I mean after all, I was accessing this member as if it was of the public interface type (I thought dynamic was simply syntactic sugar for reflection) but the dynamic binder cannot make such an assumption unless you give it a hint, using a simple cast.我有很多 JavaScript 经验,作为一个 JavaScript 程序员,其中真的没有公共或私有之类的东西,我完全被可见性很重要的事实所迷惑,我的意思是毕竟,我正在访问这个成员,好像它是公共接口类型(我认为动态只是反射的语法糖)但动态绑定器不能做出这样的假设,除非你给它一个提示,使用简单的转换。

The fundamental principle of "dynamic" in C# is: at runtime do the type analysis of the expression as though the runtime type had been the compile time type . C# 中“动态”的基本原则是:在运行时对表达式进行类型分析,就好像运行时类型是编译时类型一样 So let's see what would happen if we actually did that:那么让我们看看如果我们真的这样做会发生什么:

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

That program would fail because Empty is not accessible.该程序将失败,因为Empty不可访问。 dynamic will not allow you to do an analysis that would have been illegal in the first place. dynamic不允许您进行一开始就非法的分析。

However, the runtime analyzer realizes this and decides to cheat a little.然而,运行时分析器意识到了这一点并决定作弊。 It asks itself "is there a base class of Empty that is accessible?"它问自己“是否有可访问的 Empty 基类?” and the answer is obviously yes.答案显然是肯定的。 So it decides to fall back to the base class and analyzes:所以它决定回退到基类并分析:

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

Which fails because that program would give you an "object doesn't have a member called Value" error.失败是因为该程序会给您一个“对象没有名为 Value 的成员”错误。 Which is the error you are getting.这是你得到的错误。

The dynamic analysis never says "oh, you must have meant"动态分析永远不会说“哦,你一定是这个意思”

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

because of course if that's what you had meant, that's what you would have written in the first place .因为当然,如果这就是您的意思,那您一开始就会这么写 Again, the purpose of dynamic is to answer the question what would have happened had the compiler known the runtime type , and casting to an interface doesn't give you the runtime type.同样, dynamic的目的是回答如果编译器知道运行时类型会发生什么的问题,并且转换到接口不会给你运行时类型。

When you move Empty outside then the dynamic runtime analyzer pretends that you wrote:当您将Empty移到外面时,动态运行时分析器会假装您写道:

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

And now Empty is accessible and the cast is legal, so you get the expected result.现在Empty可以访问并且强制转换是合法的,所以你得到了预期的结果。


UPDATE:更新:

can compile that code into an assembly, reference this assembly and it will work if the Empty type is outside of the class which would make it internal by default可以将该代码编译为程序集,引用此程序集,如果 Empty 类型在类之外,默认情况下将使其成为内部类型,它将起作用

I am unable to reproduce the described behaviour.我无法重现所描述的行为。 Let's try a little example:让我们尝试一个小例子:

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'

And you see how this works: the runtime binder has detected that InternalThing is internal to the foreign assembly, and therefore is inaccessible in foo.exe.你会看到它是如何工作的:运行时绑定器检测到InternalThing是外部程序集的内部,因此在 foo.exe 中无法访问。 So it falls back to the public base type, Thing , which is accessible but does not have the necessary property.所以它回退到公共基类型Thing ,它可以访问但没有必要的属性。

I'm unable to reproduce the behaviour you describe, and if you can reproduce it then you've found a bug.我无法重现您描述的行为,如果您可以重现它,那么您就发现了一个错误。 If you have a small repro of the bug I am happy to pass it along to my former colleagues.如果您有该错误的小副本,我很乐意将其传递给我以前的同事。

I guess, at runtime, container method calls are just resolved in the private Empty class, which makes your code fail.我猜,在运行时,容器方法调用只是在私有 Empty 类中解析,这会使您的代码失败。 As far as I know, dynamic can not be used to access private members (or public members of private class)据我所知,动态不能用于访问私有成员(或私有类的公共成员)

This should (of course) work :这应该(当然)工作:

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

Here, it is class Empty which is private : so you can not manipulate Empty instances outside of the declaring class (factory).在这里,它是私有的 Empty 类:因此您不能在声明类(工厂)之外操作 Empty 实例。 That's why your code fails.这就是您的代码失败的原因。

If Empty were internal, you would be able to manipulate its instances accross the whole assembly, (well, not really because Factory is private) making all dynamic calls allowed, and your code work.如果 Empty 是内部的,您将能够在整个程序集中操作它的实例(好吧,并不是因为 Factory 是私有的)允许所有动态调用,并且您的代码可以工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM