1

If you run TestClass.Test() you will get a RuntimeBinderException. It all starts with var str = DoSomething(obj); implicitly typing to dynamic rather than string. Can someone explain what is happening here? Why does RequiresString(str); work? I understand that you can't call extension methods on dynamic objects, but this whole thing feels a bit dirty and broken to me. It all compiles fine despite obvious type mismatches then fails at runtime.

public static class ExtensionTest
{
    public static string ToJsonTest(this object x)
    {
        return string.Empty;
    }
}
public static class TestClass
{
    public static void Test()
    {
        dynamic obj = new ExpandoObject();
        obj.var1 = "hello";

        var str = DoSomething(obj);
        var testObj = RequiresString(str);
        var json = testObj.ToJsonTest();
    }
    public static string DoSomething(object x)
    {
        return string.Empty;
    }
    public static TestObj RequiresString(string x)
    {
        return new TestObj();
    }
    public class TestObj
    {
        public int Prop1 { get; set; }
    }
}
argyle
  • 1,319
  • 2
  • 14
  • 28
  • 1
    it is dirty & I'm not sure this fully answers your question but here's a blurb discussing how extension methods work in the CLR and why they don't with the DLR: https://stackoverflow.com/a/5313149/1186321 – denvercoder9 Feb 26 '19 at 21:02
  • To be clear, for anyone else, to modify the declaration of "str" above to be explicitly typed solves the problem, ie: ```string str = DoSomething(obj);``` of the "contagious dynamic type" described in the article Vlad linked in a comment to his answer below. Just in case someone came to the conclusion that dynamic is useless - in cases like this, don't declare variables using 'var'. – argyle Feb 27 '19 at 12:27

1 Answers1

2

The call to RequiresString contains dynamic arguments, so it's resolved at runtime using the actual argument's type. The actual type returned by DoSomething is string, so the runtime binder looks for RequiresString(string), which can be successfully found as TestClass.RequiresString. So far, so good.

The next call testObj.ToJsonTest() fails, as you already mentioned, because extension methods on dynamic targets are not resolved, so the next call fails.

Yes, it might feel a little bit dirty because there are (almost) no compile-time checks when dynamics are involved, very much like weakly typed scripting languages. That's why I'd advise using dynamic only if really needed and in the narrowest possible context; in particular, I'd say dynamic should not be seen on class' public surface.

Vlad
  • 35,022
  • 6
  • 77
  • 199
  • The thing that confuses me is why DoSomething(), which returns a string can somehow actually be interpreted to return a dynamic. It doesn't. It takes an object and returns a string. But somehow and for some reason the return type is changed. There's only one DoSomething() defined and it returns a string, not a dynamic. Merely passing a dynamic object to a function that expects an object somehow magically and effectively changes the return type of the function. Is some DoSomething`1() being compiled in? That's still nonsense to me. – argyle Feb 27 '19 at 00:52
  • 1
    @jeromeyers: Well, there is always a possibility that there are multiple overloads having different return types. In that case we cannot predict the real type so must be dynamic. It would be quite strange if the type of the result depends on the number of overloads. And by the way, the call may return a _more specific_ type, so having it dynamic will possibly result in other overload being chosen on the next call. – Vlad Feb 27 '19 at 02:13
  • 1
    @jeromeyers: There is a good article by Eric Lippert about this: https://blogs.msdn.microsoft.com/ericlippert/2012/11/08/dynamic-contagion-part-two/ – Vlad Feb 27 '19 at 02:13
  • Nice article - exactly what was needed. Thank you! – argyle Feb 27 '19 at 04:30