Подтвердить что ты не робот

Кастинг Dynamic и var для Object в С#

Рассмотрим следующие функции:

static void Take(object o)
{
    Console.WriteLine("Received an object");
}

static void Take(int i)
{
    Console.WriteLine("Received an integer");
}

Когда я вызываю функцию Take следующим образом:

var a = (object)2;
Take(a);

Я получаю: Received an object

Но если назовите его так:

dynamic b = (object) 2;
Take(b);

Я получаю: Received an integer

Оба параметра (a и b) добавляются к object. Но почему компилятор имеет такое поведение?

4b9b3361

Ответ 1

var - это просто синтаксический сахар, чтобы тип определялся RHS.

В вашем коде:

var a = (object)2;

эквивалентно:

object a = (object)2;

Вы получаете объект, так как вы помещаете 2 в объект.

Для dynamic вы можете посмотреть Использование динамического типа. Обратите внимание: тип - это статический тип, но объект типа dynamic обходит проверку статического типа, то есть тип, который вы указали:

dynamic b = (object) 2;

обходит, и реальный тип его разрешается во время выполнения.


Как он разрешился во время выполнения, я считаю это намного сложнее, чем вы можете себе представить.

Скажите, что у вас есть следующий код:

public static class TestClass {
    public static void Take(object o) {
        Console.WriteLine("Received an object");
    }

    public static void Take(int i) {
        Console.WriteLine("Received an integer");
    }

    public static void TestMethod() {
        var a=(object)2;
        Take(a);

        dynamic b=(object)2;
        Take(b);
    }
}

и я положил полный IL (отладочной конфигурации) в конце моего ответа.

Для этих двух строк:

var a=(object)2;
Take(a);

IL только:

IL_0001: ldc.i4.2
IL_0002: box [mscorlib]System.Int32
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: call void TestClass::Take(object)

Но для этих двух:

dynamic b=(object)2;
Take(b);

от IL_000f до IL_007a от TestMethod. Он не вызывает напрямую Take(object) или Take(int), но вызывает метод следующим образом:

object b = 2;
if (TestClass.<TestMethod>o__SiteContainer0.<>p__Site1 == null)
{
    TestClass.<TestMethod>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Take", null, typeof(TestClass), new CSharpArgumentInfo[]
    {
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null),
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
    }));
}
TestClass.<TestMethod>o__SiteContainer0.<>p__Site1.Target(TestClass.<TestMethod>o__SiteContainer0.<>p__Site1, typeof(TestClass), b);

Полный IL из TestClass:

.class public auto ansi abstract sealed beforefieldinit TestClass
    extends [mscorlib]System.Object
{
    // Nested Types
    .class nested private auto ansi abstract sealed beforefieldinit '<TestMethod>o__SiteContainer0'
        extends [mscorlib]System.Object
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Fields
        .field public static class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> '<>p__Site1'

    } // end of class <TestMethod>o__SiteContainer0


    // Methods
    .method public hidebysig static 
        void Take (
            object o
        ) cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 13 (0xd)
        .maxstack 8

        IL_0000: nop
        IL_0001: ldstr "Received an object"
        IL_0006: call void [mscorlib]System.Console::WriteLine(string)
        IL_000b: nop
        IL_000c: ret
    } // end of method TestClass::Take

    .method public hidebysig static 
        void Take (
            int32 i
        ) cil managed 
    {
        // Method begins at RVA 0x205e
        // Code size 13 (0xd)
        .maxstack 8

        IL_0000: nop
        IL_0001: ldstr "Received an integer"
        IL_0006: call void [mscorlib]System.Console::WriteLine(string)
        IL_000b: nop
        IL_000c: ret
    } // end of method TestClass::Take

    .method public hidebysig static 
        void TestMethod () cil managed 
    {
        // Method begins at RVA 0x206c
        // Code size 129 (0x81)
        .maxstack 8
        .locals init (
            [0] object a,
            [1] object b,
            [2] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000
        )

        IL_0000: nop
        IL_0001: ldc.i4.2
        IL_0002: box [mscorlib]System.Int32
        IL_0007: stloc.0
        IL_0008: ldloc.0
        IL_0009: call void TestClass::Take(object)
        IL_000e: nop
        IL_000f: ldc.i4.2
        IL_0010: box [mscorlib]System.Int32
        IL_0015: stloc.1
        IL_0016: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> TestClass/'<TestMethod>o__SiteContainer0'::'<>p__Site1'
        IL_001b: brtrue.s IL_0060

        IL_001d: ldc.i4 256
        IL_0022: ldstr "Take"
        IL_0027: ldnull
        IL_0028: ldtoken TestClass
        IL_002d: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
        IL_0032: ldc.i4.2
        IL_0033: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
        IL_0038: stloc.2
        IL_0039: ldloc.2
        IL_003a: ldc.i4.0
        IL_003b: ldc.i4.s 33
        IL_003d: ldnull
        IL_003e: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
        IL_0043: stelem.ref
        IL_0044: ldloc.2
        IL_0045: ldc.i4.1
        IL_0046: ldc.i4.0
        IL_0047: ldnull
        IL_0048: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
        IL_004d: stelem.ref
        IL_004e: ldloc.2
        IL_004f: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>, class [mscorlib]System.Type, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
        IL_0054: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
        IL_0059: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> TestClass/'<TestMethod>o__SiteContainer0'::'<>p__Site1'
        IL_005e: br.s IL_0060

        IL_0060: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> TestClass/'<TestMethod>o__SiteContainer0'::'<>p__Site1'
        IL_0065: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>>::Target
        IL_006a: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> TestClass/'<TestMethod>o__SiteContainer0'::'<>p__Site1'
        IL_006f: ldtoken TestClass
        IL_0074: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
        IL_0079: ldloc.1
        IL_007a: callvirt instance void class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>::Invoke(!0, !1, !2)
        IL_007f: nop
        IL_0080: ret
    } // end of method TestClass::TestMethod

} // end of class TestClass

Ответ 2

динамическая:

  • dynamic - это Dynamically typed
  • Динамически типизированный - это означает, что тип объявленной переменной определяется компилятором во время выполнения.

вар:

  • var - это Statically typed
  • Статически типизированный - это означает, что тип объявленной переменной определяется компилятором во время компиляции.

Таким образом, вы видите, что разрешение перегрузки происходит во время выполнения для dynamic.

Таким образом, переменная b сохраняется как int

dynamic b = (object) 2;
Take(b);

Вот почему Take(b); вызывает Take(int i)

static void Take(int i)
    {
        Console.WriteLine("Received an integer");
    }

Но в случае var a = (object)2 переменная a выполняется как "объект"

var a = (object)2;
Take(a);

Вот почему Take (a); вызывает Take(object o)

static void Take(object o)
    {
        Console.WriteLine("Received an object");
    }

Ответ 3

Разрешение целочисленного аргумента в штучной упаковке происходит во время компиляции. Вот IL:

IL_000d:  box        [mscorlib]System.Int32
IL_0012:  stloc.0
IL_0013:  ldloc.0
IL_0014:  call       void ConsoleApp.Program::Take(object)

Вы можете видеть, что он разрешен к перегрузке object во время самого компиляции.

Когда вы используете dynamic - в изображение входит связующее. dynamic может не только разрешать управляемые объекты С#, но также и не управляемые объекты, такие как COM-объекты или объекты JavaScript, при условии, что для этих объектов существует связующее средство времени выполнения.

Вместо того, чтобы показывать IL, я покажу декомпилированный код (более простой для чтения):

   object obj3 = 2;
        if (<Main>o__SiteContainer0.<>p__Site1 == null)
        {
            <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Take", null, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
        }
        <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, typeof(Program), obj3);

Вы видите, что метод Take разрешен во время выполнения привязкой времени выполнения, а не компилятором. Таким образом, он разрешит его фактическому типу.

Ответ 4

Если вы посмотрите на спецификацию С#:

1.6.6.5 Перегрузка метода

Перегрузка метода позволяет нескольким методам в одном классе иметь одно и то же имя, если у них есть уникальные подписи. При компиляции вызова перегруженного метода компилятор использует разрешение перегрузки для определения конкретного метода для вызова.

и

7.5.4 Проверка времени динамической перегрузки времени компиляции

Для большинства динамически связанных операций набор возможных кандидатов для разрешения неизвестен в compiletime. В некоторых случаях, однако, набор кандидатов известен во время компиляции:

  • Статические вызовы методов с динамическими аргументами

  • Метод вызова экземпляра, где получатель не является динамическим выражением

  • Индексатор вызывает, где получатель не является динамическим выражением

  • Конструкторские вызовы с динамическими аргументами

В этих случаях для каждого кандидата выполняется ограниченная проверка времени компиляции, чтобы узнать, может ли кто-либо из них применить во время выполнения

Итак, в вашем первом случае var не является динамическим, разрешение перегрузки найдет метод перегрузки во время компиляции.

Но в вашем втором случае вы вызываете статический метод с динамическими аргументами, разрешение перегрузки будет искать метод перегрузки во время выполнения вместо

Ответ 5

Чтобы понять разрешение типа в вашем случае для динамических переменных.

  • Сначала поставьте точку прерывания в строке:

    Take(b); //See here the type of b when u hover mouse over it, will be int

что явно означает, что код: dynamic b = (object)2 не конвертирует 2 объекту при назначении динамической переменной и b остается int

  • Затем закомментируйте вашу перегрузку метода Take(int i) и затем поместите точку останова в строке Take(b) (то же: тип b по-прежнему int), но когда вы запустите его, вы увидите, что напечатанное значение: Получено объект.

  • Теперь измените вызов переменной dynamic на код ниже:

    Take((object)b); //It now prints "Received an object"

  • Затем измените свой вызов на код ниже и посмотрите, что возвращается:

    dynamic b = (long)2;

    Take(b); // It now prints Received an object because there is no method overload that accepts a long and best matching overload is one that accepts an объект .

Это связано с тем, что: наилучший тип соответствия разрешен для динамического переменные в соответствии со значением, которое она хранит во время выполнения, а также метод наилучшего совпадающего метода перегрузки, который разрешен во время выполнения для динамических переменных.

Ответ 6

В первом случае var означает object, поэтому вызывается Take(object o). Во втором случае вы используете тип dynamic, и, несмотря на то, что вы ввели в коробку свой int, он все еще имеет информацию об этом типе - int, поэтому вызывается метод наилучшего соответствия.