简体   繁体   中英

The dynamic binding used instead of the extension method for strictly typed value in C#

I have a short-expression that returns an int value and I'm trying to call the extension method on the result value, but when any dynamic variable is present in inner expressions, the dynamic binder is used and I receive an exception Microsoft.CSharp.RuntimeBinder.RuntimeBinderException with the message that

'int' does not contain a definition for 'AsStringOrNull'

Here is all code that reproduces the issue:

using System;

namespace ExtensionMethodsAndDynamic
{

    static class Extensions
    {
        public static string AsStringOrNull(this object value) => value?.ToString();
    }

    class Program
    {
        public static object Foo(object value) => value;
        static int GetIntFrom(object anything) => 1;


        static void Main()
        {
            dynamic dynamicVariable = null;

            string result1 = GetIntFrom((object)dynamicVariable).AsStringOrNull();// Works as expected
            string result2 = GetIntFrom(dynamicVariable).AsStringOrNull();        
            string result3 = GetIntFrom(Foo(dynamicVariable)).AsStringOrNull();   
        }
    }
}

Also, I noticed another behavior I can not understand.
The function is successfully called with a strictly typed argument
The same doesn't work for a dynamic argument
But it will work if we specify the function

Please help me to understand why the stuff related to dynamic variables spreads outside the call of this function int GetIntFrom(object anything) .

Extension methods are evaluated at compile time and replaced with the appropriate static call. But when you use dynamic , the call is instead resolved at runtime. This means that when you type "AsStringOrNull" after a dynamic variable, you are doing something completely different from when you type it after a statically-typed variable.

After the compiler has finished its work, the code that actually executes works a little bit like this:

static class Extensions
{
    // Note the absent "this" keyword. It's just an ordinary method after compilation
    public static string AsStringOrNull(object value) => value?.ToString();
}

class Program
{
    public static object Foo(object value) => value;
    static int GetIntFrom(object anything) => 1;


    static void Main()
    {
        dynamic dynamicVariable = null;

        string result1 = Extensions.AsStringOrNull(GetIntFrom((object)dynamicVariable));

        object intObj = GetIntFrom(dynamicVariable); // this step is actually much more complicated
        string result2 = intObj.GetType().GetMethod("AsStringOrNull")?.Invoke(intObj) ?? throw new RuntimeBinderException();
    }
}

For brevity's sake I won't type out what happens for result3 . The above should already make it clear why the exception is thrown. You just can't use static compiler sugar when any dynamic variable is involved in method resolution.

The IDE won't generate any errors here because dynamic objects are for obvious reasons not checked for errors at all. But if you try to use Go To Reference / F12 on the second and third invocations of "AsStringOrNull", you should see that they aren't resolved.

Now, I think I understand why it works this way. The compiler produces the code that will use the best overload for this function in runtime based on dynamic parameter true type. Since the return type can vary for different overloads it can't be determined at the compile-time, so the following sections (method calls, extension method calls , property access, etc.) can't rely on any strict type and have to assume dynamic.

using System;

namespace ExtensionMethodsAndDynamic
{
    class Program
    {
        static int GetIntFrom(object anything) => 1;
        static int GetIntFrom(int anything) => 2;
        static int GetIntFrom(string anything) => 3;
        static string GetIntFrom(object[] anything) => "4";


        static void Main()
        {
            Console.WriteLine(GetIntFrom((dynamic)(new object())));  // 1
            Console.WriteLine(GetIntFrom((dynamic)1));               // 2
            Console.WriteLine(GetIntFrom((dynamic)"1"));             // 3
            Console.WriteLine(GetIntFrom((dynamic)(new object[0]))); // 4
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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