【问题标题】:Casting Dynamic and var to Object in C#在 C# 中将 Dynamic 和 var 转换为 Object
【发布时间】:2013-06-21 21:25:00
【问题描述】:

考虑这些函数:

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。但是为什么编译器会有这种行为呢?

【问题讨论】:

  • a 具有编译时类型 object。这是一个普通的 static C# 类型,因此对特定重载的 binding 发生在基于编译时类型的编译时。因为那是object,所以这是一个简单的选择。另一方面,b 具有编译时类型dynamic。对object 的装箱是多余的。请记住dynamic 等于object,只是绑定延迟到运行时。 dynamic 的整个想法是不跟踪类型编译时,然后等待绑定直到运行时。由于运行时类型是Int32,所以很简单。

标签: c# dynamic


【解决方案1】:

var 只是一个语法糖,让RHS 决定类型。

在您的代码中:

var a = (object)2;

相当于:

object a = (object)2;

你得到一个对象,因为你把2 装箱到一个对象上。

对于dynamic,您可能想查看Using Type 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_000fIL_007aTestMethod。它不直接调用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);

TestClass的完整IL:

.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

【讨论】:

  • 对于重载解决方案,wiki 上有很好的解释 -- C Sharp 4.0
  • 对于编译器生成的名字,看看Lippert先生的回答:stackoverflow.com/questions/2508828/…
  • 复杂的重载分辨率对于dynamic 与通常的非动态类型(如对象)相同。唯一的区别是对于dynamic,这种决定过载的复杂算法直到运行时才会发生,具体取决于实际的运行时类型(如GetType() 输出)。对于非动态类型,相同的绑定发生在编译时,基于变量或表达式的声明的类型(编译时类型)。
【解决方案2】:

帮助您了解动态变量的类型解析。

  • 先在该行打一个断点:

    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) 但是当你运行它时,你会看到打印出来的值是:Recieved 对象。

  • 现在,将您的 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 anobject.

这是因为:为动态解析最佳匹配类型 变量根据它在运行时持有的值,并且在运行时为动态变量解析要调用的最佳匹配重载方法。

【讨论】:

    【解决方案3】:

    如果你看一下 C# 规范:

    1.6.6.5 方法重载

    方法重载允许同一类中的多个方法具有相同的名称,只要它们具有唯一的签名即可。在编译重载方法的调用时,编译器使用重载决议来确定要调用的具体方法。

    还有:

    7.5.4 动态重载解析的编译时检查

    对于大多数动态绑定操作,可能的解析候选集在编译时是未知的。然而,在某些情况下,候选集在编译时是已知的:

    • 使用动态参数调用静态方法

    • 接收者不是动态表达式的实例方法调用

    • 接收者不是动态表达式的索引器调用

    • 使用动态参数调用构造函数

    在这些情况下,对每个候选者执行有限的编译时检查,以查看其中是否有可能在运行时应用

    所以,在您的第一种情况下,var 不是动态的,重载解析会在编译时找到重载方法。

    但在第二种情况下,您正在调用 带有动态参数的静态方法重载解析会在运行时找到重载方法

    【讨论】:

    • 我认为问题是它在运行时绑定的时间..为什么对对象的显式强制转换不生效?
    • @SimonWhitehead:我认为这个答案有助于解决重载问题,这在其他答案中没有明显显示,即使是我的。
    【解决方案4】:

    动态:

    1. dynamicDynamically typed
    2. 动态类型 - 这意味着声明的变量类型由编译器在运行时决定。

    变量:

    1. varStatically typed
    2. 静态类型——这意味着声明的变量类型由编译器在编译时决定。

    由此,您会看到 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");
        }
    

    【讨论】:

    • 你对dynamic的解释不太对:变量的类型 object。但是,当在表达式中使用该变量时,将导致编译器在运行时被调用,并根据存在的运行时类型(粗略地说)执行表达式的评估。
    • 是我还是这实际上没有回答问题?
    • @ppejovic 不,不只是,你,我读了 10 遍,没有看到解释,但也许我只是一个白痴……
    • @DimitarDimitrov:一个对象实例在运行时只能是一种类型。不仅dynamicobject a = (object)2; a.GetType(); 也会在 runtime 给你System.Int32。只是第一次重载解析是由编译器在编译时完成的,这就是它解析为object重载的原因。
    • the compiler at run time 如何在运行时调用编译器,它可能是解析该动态变量类型的 C# VM 或 C# 解释器。
    【解决方案5】:

    盒装整数参数解析发生在编译时。这是 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 不仅可以解析为托管的 C# 对象,还可以解析为非托管对象,例如 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 方法在运行时由运行时绑定程序而不是由编译器解析。因此,它会将其解析为实际类型。

    【讨论】:

      【解决方案6】:

      在第一种情况下,var 表示object,因此调用了Take(object o)。在第二种情况下,您使用 dynamic 类型,尽管您已将 int 装箱,但它仍然具有有关其类型的信息 -int 因此调用了最佳匹配方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-29
        • 2017-07-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多