C# dynamic type gotcha

后端 未结 2 1064
无人共我
无人共我 2020-12-05 10:40

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 <

相关标签:
2条回答
  • 2020-12-05 11:35

    I guess, at runtime, container method calls are just resolved in the private Empty class, which makes your code fail. 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). 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.

    0 讨论(0)
  • 2020-12-05 11:42

    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. 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. dynamic will not allow you to do an analysis that would have been illegal in the first place.

    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?" 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. 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.

    When you move Empty outside then the dynamic runtime analyzer pretends that you wrote:

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

    And now Empty is accessible and the cast is legal, so you get the expected result.


    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

    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. So it falls back to the public base type, Thing, which is accessible but does not have the necessary property.

    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.

    0 讨论(0)
提交回复
热议问题