19

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. '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:

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

Read Eric Lippert's answer for a good explanation of the rest.

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.

John Leidegren
  • 59,920
  • 20
  • 131
  • 152
  • 6
    What if it's `public class Empty : IContainer` else isn't it just private to the factory class? Probably why it can't find the implementing class to bind to. – Lloyd Mar 11 '13 at 12:23
  • 2
    Make your class Empty public – VARAK Mar 11 '13 at 12:24
  • lol... What Lloyd said – VARAK Mar 11 '13 at 12:24
  • I tried to make `Empty` public and it works – Anri Mar 11 '13 at 12:28
  • Making the class `public` works just as well as moving the class outside of the class scope, but it does not explain why dynamic types have to be `internal` (`internal` seems to work too). Making the class `public` completely breaks encapsulation, so I wouldn't wanna do that. – John Leidegren Mar 11 '13 at 12:53
  • What doesn't make sense to me right now is that `internal` (or assembly private) and class private (the nested case) isn't treated equally. Either way the class isn't visible outside of the defining assembly even if it's exposed as using a public interface type. How is it that the run-time binding doesn't get that? – John Leidegren Mar 11 '13 at 12:55
  • 1
    Look here: [Dynamic Gotchas](http://csharpindepth.com/Articles/Chapter14/DynamicGotchas.aspx). – Omar Mar 11 '13 at 13:10
  • @Fuex but we're not using explicit interface implementation in this case. While an intresting read, I don't think it's relevent in this case. e.g. if it was, then changing the visibility (private/internal/public) wouldn't matter. – John Leidegren Mar 11 '13 at 13:48
  • 1
    I guess what @Fuex wanted to illustrate is that there are situations where you can do "less" things with dynamics. The Array.Count illustrates that, even though the problem is different. Anyway, accessing an internal class of a private class with dynamics is a gotcha IMHO. It would still be interesting without interface, with just Empty and Value being internal. – jbl Mar 11 '13 at 14:00
  • 2
    The odd behavior is correct. I'll write up an explanation when I'm not on a bus. – Eric Lippert Mar 11 '13 at 14:14
  • @EricLippert much obliged. I don't doubt that it is correct but I look at the type system and go, hu? Why would class private break the damn thing. – John Leidegren Mar 11 '13 at 14:45

2 Answers2

15

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    @jonskeet: You might want to add this one to your "dynamic gotchas" page. – Eric Lippert Mar 11 '13 at 15:06
  • ...but why does it make a distinction between internal and private. In my world both of these types (as a consumer of an assembly) are invisible to me, yet it works when the type in question is internal but fails if it is a nested private member. This I do not get. – John Leidegren Mar 11 '13 at 15:07
  • In my question, I can compile that code into an assembly, reference this assembly and it will work if the `Empty` type is nested and at least internal or outside of the class (which would make it, at least internal by default). – John Leidegren Mar 11 '13 at 15:15
  • Actually, this just got worse, I cannot reproduce the issue with internal like I just said (which wouldn't make sense) but I'm able to do it if the assembly is being built through CSharpCodeProvider class. Somehow, I seem to have access to the internals in this case through the dynamic type... – John Leidegren Mar 11 '13 at 15:25
  • @JohnLeidegren: I can't reproduce it either. I've updated my answer with an attempt at reproducing it. If you have a small repro case I am happy to provide an opinion on it. – Eric Lippert Mar 11 '13 at 15:28
  • @JohnLeidegren: CodeDOM doesn't produce any code that you couldn't produce "by hand", so there's something unexpected going on there. – Eric Lippert Mar 11 '13 at 15:31
  • Let me get back to you with a solid test case. – John Leidegren Mar 11 '13 at 15:44
  • 2
    Oh my, wild goose chase over, this was only happening in my test project and of course my test project has been marked with the `InternalsVisibleToAttribute`. Go figure. – John Leidegren Mar 11 '13 at 16:01
  • @JohnLeidegren: Well then the behaviour is by design; the internals are visible and so the dynamic analyzer will use the internal foreign class no problem. – Eric Lippert Mar 11 '13 at 16:06
  • It's counterintuitive that this "dynamic" feature is trying to use compile-time semantics. I wrote a helper assembly that accepts a couple Type parameters, generates Tasks with those Types as their return types, and now I discover that the cast to dynamic to access their "Result" property will fail for *some* types. In other words, the dynamic keyword is not the shortcut people have made it out to be. It's not "reflection with the advantage of call-site caching". It's considerably crippled compared to reflection, because of this pretend-its-compile-time feature. – Triynko Oct 30 '18 at 03:55
  • @Triynko: Well, first, the feature was never intended to be a replacement for reflection, so the fact that it fails to be something it was not built to be is unsurprising. Second, though I always appreciate user feedback, the ideal time to give me that feedback would have been in 2009 when I was soliciting feedback on the design of the feature, and not eight years after it shipped and six years after I left Microsoft. I'm not quite sure what you want me to do with your comment now. – Eric Lippert Oct 30 '18 at 04:33
2

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.

jbl
  • 15,179
  • 3
  • 34
  • 101
  • It does, but how on earth does it managed to figure out that the method which is an (public interface method) is somehow private and cannot be called? It makes absolutely no sense at all. Especially since making it internal (which makes it assembly private) does not cause the same problem. – John Leidegren Mar 11 '13 at 12:59
  • Why is the dynamic binder making a distinction between private and internal... :/ – John Leidegren Mar 11 '13 at 13:01
  • But this is illogical - you can openly access the property through via and can't via dynamic resolution. – Andrey Mar 11 '13 at 13:14
  • @JohnLeidegren have a look at Fuex comment. The example on Array.Count is as illogical, and the last sentence of the conclusion, is a pretty good sum-up of dynamics ;-) – jbl Mar 11 '13 at 13:17
  • @jbl Read my followup to Fuex's comment. We're not using an explicit interface implementation and if we were then changing the visibility shouldn't have made a difference. – John Leidegren Mar 11 '13 at 13:50