5

I have encountered issues with using dynamic variables in C#. This arose while coding NancyFx routing modules, but I have boiled down the issue to the example below. While I received a different exception in the original code, the example code still throws an exception that I believe is erroneous. Does anyone see what is going on here, or have others encountered similar problems?

Note that the following posts may be related: StackOverflowException when accessing member of generic type via dynamic: .NET/C# framework bug? System.Dynamic bug?

The code:

class Program
{
    static void Main(string[] args)
    {
        var dictionary = new Dictionary<string, object>();
        dictionary.Add("number", 12);
        var result = MethodUsesExplicitDeclaration(dictionary);
        var result2 = MethodUsesImplicitDeclaration(dictionary);
    }

    static dynamic MethodUsesExplicitDeclaration(dynamic reallyDictionary)
    {
        // this works, ostensibly because the local variable is explicitly declared
        IDictionary<string, object> dictionary = CastDictionary(reallyDictionary);
        return dictionary.Get<int>("number");
    }

    static dynamic MethodUsesImplicitDeclaration(dynamic reallyDictionary)
    {
        // this throws an exception, and the only difference is 
        // that the variable declaration is implicit
        var dictionary = CastDictionary(reallyDictionary);
        return dictionary.Get<int>("number");
    }

    static IDictionary<string, object> CastDictionary(dynamic arg)
    {
        return arg as IDictionary<string, object>;
    }
}

static class Extensions
{
    public static T Get<T>(this IDictionary<string, object> dictionary, string key)
    {
        var value = dictionary[key];
        if (value is T)
            return (T)value;
        throw new InvalidOperationException();
    }
}

The exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException was unhandled HResult=-2146233088 Message='System.Collections.Generic.Dictionary<string,object>' does not contain a definition for 'Get' Source=Anonymously Hosted DynamicMethods Assembly StackTrace: at CallSite.Target(Closure , CallSite , Object , String ) at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1) at DynamicBug.Program.MethodUsesImplicitDeclaration(Object reallyDictionary) in c:\TFS\UnreleasedCode\POC\DynamicBug\DynamicBug\Program.cs:line 28 at DynamicBug.Program.Main(String[] args) in c:\TFS\UnreleasedCode\POC\DynamicBug\DynamicBug\Program.cs:line 16 InnerException:

Community
  • 1
  • 1
Jason Mowry
  • 51
  • 1
  • 3
  • 1
    Seems like this question is answered [here](http://stackoverflow.com/a/258999/97382). – Craig W. Jul 15 '14 at 14:56
  • 1
    I don't believe that http://stackoverflow.com/questions/258988/will-the-dynamic-keyword-in-c4-support-extension-methods addresses the same question. The example code does not try to use an extension method on a variable declared as dynamic, it uses it on the result of a method which returns an interface type. So in both versions of the method which executes an extension method the type to which the extension method applies is not dynamic. – Jason Mowry Jul 15 '14 at 15:02
  • Another way I could have asked the original question is when should a `var` variable declaration not behave like the type of the value you assign to it? – Jason Mowry Jul 15 '14 at 15:05
  • 2
    The first part of Jon Skeet's quoted response states, "Dynamic lookup will not be able to find extension methods." You're getting an error that `Get` does not exist. `Get` is an extension method. It seems like it applies just fine. If you change that line to `return Extensions.Get(dictionary, "number");` then it executes as expected. – Craig W. Jul 15 '14 at 15:26
  • [Cédric Bignon's answer](http://stackoverflow.com/a/14535956/3) explains why this occurs, including where in the C# spec this behavior is defined. – Jarrod Dixon Jul 23 '14 at 21:34

1 Answers1

5

The problem is that when you don't explicitly assigned the object to a IDictionary<string,object> declaration, the object will still be a dynamic type (see the type resolution of in the images).

As Eric Lippert points out in https://stackoverflow.com/a/5313149/1039903 extension methods will not be available at the call site, for dynamic types:

To expand on Jon's answer, the reason this doesn't work is because in regular, non-dynamic code extension methods work by doing a full search of all the classes known to the compiler for a static class that has an extension method that match. The search goes in order based on the namespace nesting and available "using" directives in each namespace.

That means that in order to get a dynamic extension method invocation resolved correctly, somehow the DLR has to know at runtime what all the namespace nestings and "using" directives were in your source code. We do not have a mechanism handy for encoding all that information into the call site. We considered inventing such a mechanism, but decided that it was too high cost and produced too much schedule risk to be worth it.

I loaded your code up into a test method to show you what is actually resolved in var dictionary during runtime compared to an explicit declaration.

Explicit declaration:

enter image description here enter image description here

Implicit declaration

enter image description here enter image description here

If you take a look at the Type resolution of dictionary the explicit declaration has type System.Collection.Generic.IDictionary and the implicit declaration has type dynamic{System.Collections.Generic.Dictionary}

Community
  • 1
  • 1
wbennett
  • 2,545
  • 21
  • 12
  • I've never used the `dynamic` keyword other than playing around and this is great info to know. Is there no way to convert it back to a static type once it is dynamic? – TyCobb Jul 15 '14 at 15:47
  • Assign it to the desired type or use the `as` keyword. (ie `var dict = CastDictionary(reallyDictionary) as IDictionary`) – wbennett Jul 15 '14 at 15:49
  • But, his `CastDictionary` is already returning an `IDictionary`. – TyCobb Jul 15 '14 at 16:00
  • It still points back to the original dynamic object, and needs to be unwrapped, or explicitly use the extension method. – wbennett Jul 15 '14 at 16:00
  • Like this (`Extensions.Get(dictionary,"number")`) – wbennett Jul 15 '14 at 16:08
  • I saw what you mean. I missed the first screenshot where `var` was not used and `dictionary` became non-dynamic. Thanks a lot. I thought the compiler would correctly infer the type off of CastDictionary, but .NET kept the type as dynamic no matter what. Thanks. – TyCobb Jul 15 '14 at 16:11
  • Yes, it still seems inconsistent to me that the var declaration does not draw its type from the declared type of the result assigned to it. Furthermore, when you move the code from CastDictionary to be inline, it works as expected. So the only thing that the runtime seems to faulter with is when the var declared variable has its value assigned from a method result instead of an inline result. Why should that behave any differently? – Jason Mowry Jul 15 '14 at 16:37
  • @JasonMowry Statically it does (that is why it compiles). During runtime, the call site has no extension information that the static type has. The inline will replace the call site with the code in the function. So the final code would result in `var dictionary = reallyDicationary as IDictionary`, which is explicit and will include the extension info. – wbennett Jul 15 '14 at 16:42