在C#中将dynamic和var投射到对象

考虑这些function:

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

两个参数( ab )都被转换为object 。 但为什么编译器有这种行为?

var只是一个语法糖,让这个types由RHS决定。

在你的代码中:

 var a = (object)2; 

相当于:

 object a = (object)2; 

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

对于dynamic ,你可能想看看使用dynamictypes 。 请注意, 该types是静态types,但dynamictypes的对象会绕过静态types检查 ,也就是您指定的types:

 dynamic b = (object) 2; 

被绕过,并且它的实际types在运行时被parsing。


对于它在运行时是如何解决的 ,我相信它比你想象的要复杂得多。

假设你有以下代码:

 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(debuggingconfiguration)放在我的答案后面。

对于这两行:

 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); 

TestMethod IL_000fIL_007a 。 它不直接调用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 

dynamic:

  1. dynamic是一个Dynamically typed
  2. dynamictypes – 这意味着声明的variablestypes是由编译器在运行时决定的。

VAR:

  1. var是一个Statically typed
  2. 静态types化 – 这意味着声明的variablestypes在编译时由编译器决定。

通过这个,你可以看到Overload的parsing发生在运行时dynamic

所以variablesb保持为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的情况下,variablesa保持为“对象”

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

这就是为什么Take(a); 来电Take(object o)

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

盒装整数参数分辨率在编译时发生。 这是IL:

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

你可以看到它在编译时本身parsing为object超载。

当您使用dynamic – 运行时联编程序进入图片。 dynamic不仅可以parsing为托pipe的C#对象,而且还可以parsingCOM对象或JavaScript对象等非托pipe对象,只要这些对象存在运行库。

而不是显示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方法在运行时由运行时绑定器parsing,而不是由编译器parsing。 所以,它会解决它的实际types。

如果你看看C#规范:

1.6.6.5方法重载

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

和:

7.5.4dynamic重载分辨率的编译时检查

对于大多数dynamic绑定的操作来说,在编译时可能的候选parsing集是未知的。 但在某些情况下,候选集在编译时是已知的:

  • 带有dynamic参数的静态方法调用

  • 实例方法调用接收者不是dynamicexpression式的地方

  • 索引器调用接收器不是一个dynamicexpression式

  • 构造函数使用dynamic参数调用

在这些情况下,对每个候选人进行有限的编译时间检查,看是否有任何候选人可能在运行时间申请

所以,在第一种情况下, var不是dynamic的, 重载parsing会在编译时find重载方法。

但在第二种情况下,您正在使用dynamic参数调用静态方法重载parsing将在运行时find重载方法

为了帮助你理解dynamicvariables的typesparsing。

  • 首先在线上放置一个断点:

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

这明显意味着代码: dynamic b = (object)2在分配给dynamicvariables时不会将2转换为对象,而b仍然是int

  • 接下来,注释掉Take(int i)方法的重载,然后在行Take(b) (同样: btypes仍然是int )上放一个断点,但是当你运行它的时候,你会看到打印的值是:Recieved object 。

  • 现在,将您的dynamicvariables调用改为下面的代码:

    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 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对象的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 .

这是因为:dynamicvariables的最佳匹配types是根据它在运行时保存的值来parsing的,而最佳匹配的被调用的重载方法是在运行时为dynamicvariablesparsing的。

在第一种情况下,var表示object所以Take(object o)被调用。 在第二种情况下,你使用dynamictypes,尽pipe你已经装箱你的int仍然有关于它的types的信息 – int所以最好的匹配方法被调用。