C#新增了dynamic關鍵字,正因為這一個小小的關鍵字,C#動態特性向前邁進 了一大步。
dynamic是一個類型關鍵字,聲明為dynamic的類型與" 靜態類型"(這裡的靜態類型是指編譯時確定的類型,下同)相比最大的特 點它是"動態類型",它會運行時嘗試調用方法,這些方法的存在與否 不是在編譯時檢查的,而是在運行時查找,如果方法存在並且參數正確,會正常 調用,否則會拋出Microsoft.CSharp.RuntimeBinder.RuntimeBinderException 異常。
看一個最簡單的示例:
using System;
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = Console.Out;
dynamic a;
a = new Int32();
int b = a;
a++;
a--;
d.WriteLine ("http://www.xianfen.net/");
d.WriteLine(d.GetType());
d.writeln("test"); //拋出 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException異常
}
}
}
對dynamic類型的操作只能有以下幾種:
·賦值
·方法調用
·自增
·自減
·接受"靜態類型"的構造器創建的對象
與關鍵 字var的比較
從表面上看,dynamic與var關鍵字的用法很像,但實質上有 本質區別。
var關鍵字被稱為:隱含類型局部變量(Local Variable Type Inference),var只能用作局部變量,不能用於字段、參數等;聲明的同時必須 初始化;初始化時類型就已經明確了,並且不能再被賦值不能進行隱式類型轉換 的類型的數據;編譯時編譯器會對var定義的變量進行類型推斷,這時變量的類 型已經被確定。
dynamic可用於類型的字段,方法參數,方法返回值,可 用於泛型的類型參數等;可以賦值給或被賦值任何類型並且不需顯式的強制類型 轉換,因為這些是運行時執行的,這要得益於dynamic類型的動態特性。
與反射的比較
首先能看到的是,dynamic與反射相比,執行相同操作所需 的代碼少的多。
如調用類Me中的GetName()方法。
class Me
{
public string Blog { get; set; }
public string GetName()
{
return "Zhenxing Zhou";
}
}
用反射調用GetName()方法:
Assembly a = Assembly.GetExecutingAssembly();
object instance = a.CreateInstance ("Xianfen.Net.TestDynamic.Me");
Type type = instance.GetType();
MethodInfo mi = type.GetMethod("GetName");
object result = mi.Invoke(instance, null);
同樣的dynamic 調用:
dynamic myInfo = new Me();
string result = myInfo.GetName();
dynamic類型與反射相比能 進行的操作要少的多。
目前dynamic類型對屬性調用是不可用的,但我們 知道,屬性生成IL時,對屬性的讀或寫會生成對應的在屬性名前加上get_或set_ 前綴生成相應的方法,嘗試調用兩個方法來訪問屬性:
dynamic myInfo = new Me();
myInfo.set_Blog("http://www.xianfen.net/");
string result = myInfo.get_Blog();
會拋出異常,提示找不到 get/set_Blog方法。這點比較遺憾,同樣,對有參屬性的訪問也是不行的。
反射還可以訪問私有方法字段以及其它類型成員及取得類型及類型成員 的信息等。
dynamic類型的效率
效率問題應該是大家很關心的, 我的感覺:不要對動態語言有很高的效率抱有太大的希望,但另一方面,算法的 設計對效率的影響非常大,功能與性能經常存在一個平衡點。
要分析其 效率,就要看看編譯後內部都干了些啥,方法是寫些簡單的代碼,查看IL。
代碼:
using System;
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = "str";
d.ToString();
}
}
}
對應的IL代碼:
.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: call instance void [mscorlib] System.Object::.ctor()
L_0006: ret
}
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 9
.locals init (
[0] object d,
[1] class [Microsoft.CSharp] Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000)
L_0000: ldstr "str"
L_0005: stloc.0
L_0006: ldsfld class [System.Core] System.Runtime.CompilerServices.CallSite`1<class [mscorlib] System.Action`2<class [System.Core] System.Runtime.CompilerServices.CallSite, object>> Xianfen.Net.TestDynamic.Program/<Main>o__SiteContainer0::<> ;p__Site1
L_000b: brtrue.s L_003f
L_000d: ldc.i4.0
L_000e: ldstr "ToString"
L_0013: ldtoken Xianfen.Net.TestDynamic.Program
L_0018: call class [mscorlib]System.Type [mscorlib] System.Type::GetTypeFromHandle(valuetype [mscorlib] System.RuntimeTypeHandle)
L_001d: ldnull
L_001e: ldc.i4.1
L_001f: newarr [Microsoft.CSharp] Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
L_0024: stloc.1
L_0025: ldloc.1
L_0026: ldc.i4.0
L_0027: ldc.i4.0
L_0028: ldnull
L_0029: newobj instance void [Microsoft.CSharp] Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::.ctor(valuetype [Microsoft.CSharp] Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
L_002e: stelem.ref
L_002f: ldloc.1
L_0030: newobj instance void [Microsoft.CSharp] Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder::.ctor (valuetype [Microsoft.CSharp] Microsoft.CSharp.RuntimeBinder.CSharpCallFlags, string, class [mscorlib]System.Type, class [mscorlib] System.Collections.Generic.IEnumerable`1<class [mscorlib] System.Type>, class [mscorlib] System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp] Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
L_0035: call class [System.Core] System.Runtime.CompilerServices.CallSite`1<!0> [System.Core] System.Runtime.CompilerServices.CallSite`1<class [mscorlib] System.Action`2<class [System.Core] System.Runtime.CompilerServices.CallSite, object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
L_003a: stsfld class [System.Core] System.Runtime.CompilerServices.CallSite`1<class [mscorlib] System.Action`2<class [System.Core] System.Runtime.CompilerServices.CallSite, object>> Xianfen.Net.TestDynamic.Program/<Main>o__SiteContainer0::<> ;p__Site1
L_003f: ldsfld class [System.Core] System.Runtime.CompilerServices.CallSite`1<class [mscorlib] System.Action`2<class [System.Core] System.Runtime.CompilerServices.CallSite, object>> Xianfen.Net.TestDynamic.Program/<Main>o__SiteContainer0::<> ;p__Site1
L_0044: ldfld !0 [System.Core] System.Runtime.CompilerServices.CallSite`1<class [mscorlib] System.Action`2<class [System.Core] System.Runtime.CompilerServices.CallSite, object>>::Target
L_0049: ldsfld class [System.Core] System.Runtime.CompilerServices.CallSite`1<class [mscorlib] System.Action`2<class [System.Core] System.Runtime.CompilerServices.CallSite, object>> Xianfen.Net.TestDynamic.Program/<Main>o__SiteContainer0::<> ;p__Site1
L_004e: ldloc.0
L_004f: callvirt instance void [mscorlib] System.Action`2<class [System.Core] System.Runtime.CompilerServices.CallSite, object>::Invoke(!0, !1)
L_0054: ret
}
.class abstract auto ansi sealed nested private beforefieldinit <Main>o__SiteContainer0
extends [mscorlib]System.Object
{
.custom instance void [mscorlib] System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
.field public static class [System.Core] System.Runtime.CompilerServices.CallSite`1<class [mscorlib] System.Action`2<class [System.Core] System.Runtime.CompilerServices.CallSite, object>> <>p__Site1
}
}
可以看出生成的IL代碼確實不美觀,不過大體能看出端倪。為 了方便查看,用Reflector查看,把反編譯結果設置為.net2.0,代碼清晰多了:
01. internal class Program
02. {
03. // Methods
04. private static void Main()
05. {
06. object d = "str";
07. if (<Main>o__SiteContainer0.<>p__Site1 == null)
08. {
09. <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.
10. Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program),
11. null, new CSharpArgumentInfo[] { new CSharpArgumentInfo (CSharpArgumentInfoFlags.None, null) }));
12. }
13. <Main>o__SiteContainer0.<>p__Site1.Target (<Main>o__SiteContainer0.<>p__Site1, d);
14. }
15. // Nested Types
16. [CompilerGenerated]
17. private static class <Main>o__SiteContainer0
18. {
19. // Fields
20. public static CallSite<Action<CallSite, object>> <>p__Site1;
21. }
22. }
06行先把賦值給dynamic的值賦給object類型,檢查編譯器 生成的靜態類<Main>o__SiteContainer0的靜態字段<>p__Site1是 否為null,如果是,則對其賦值,賦值的內容在這裡不詳細研究。然後調用 <>p__Site1進行操作。
這裡會發現兩個問題:賦值給dynamic的值 賦給object類型,對於值類型會不會執行同樣的操作,會執行裝箱操作嗎;編譯 器生成的靜態類<Main>o__SiteContainer0的靜態字段<>p__Site1 應該是緩存作用。這兩個問題稍後驗證。
1)對值類型進行的操作
如下代碼:
using System;
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = 5;
d.ToString();
}
}
}
反編譯代碼:
internal class Program
{
// Methods
private static void Main()
{
object d = 5;
if (<Main>o__SiteContainer0.<>p__Site1 == null)
{
<Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.
Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program),
null, new CSharpArgumentInfo[] { new CSharpArgumentInfo (CSharpArgumentInfoFlags.None, null) }));
}
<Main>o__SiteContainer0.<>p__Site1.Target (<Main>o__SiteContainer0.<>p__Site1, d);
}
// Nested Types
[CompilerGenerated]
private static class <Main>o__SiteContainer0
{
// Fields
public static CallSite<Action<CallSite, object>> <>p__Site1;
}
}
可見確實對值類型進行了裝箱操作,效率可想而知。
2) 編譯器生成的緩存類
代碼如下:
using System;
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = 5;
d.ToString();
d.ToString();
}
}
}
反編譯的代碼:
internal class Program
{
// Methods
private static void Main()
{
object d = 5;
if (<Main>o__SiteContainer0.<>p__Site1 == null)
{
<Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program), null, new CSharpArgumentInfo[] { new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
}
<Main>o__SiteContainer0.<>p__Site1.Target (<Main>o__SiteContainer0.<>p__Site1, d);
if (<Main>o__SiteContainer0.<>p__Site2 == null)
{
<Main>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, object>>.Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program), null, new CSharpArgumentInfo[] { new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
}
<Main>o__SiteContainer0.<>p__Site2.Target (<Main>o__SiteContainer0.<>p__Site2, d);
}
// Nested Types
[CompilerGenerated]
private static class <Main>o__SiteContainer0
{
// Fields
public static CallSite<Action<CallSite, object>> <>p__Site1;
public static CallSite<Action<CallSite, object>> <>p__Site2;
}
}
代碼調用了ToString方法,但編譯器生成了兩份緩存。
如果是在循環中:
代碼:
using System;
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = 5;
for (int i = 0; i < 100; i++)
{
d.ToString();
}
}
}
}
反編譯代碼:
internal class Program
{
// Methods
private static void Main()
{
object d = 5;
for (int i = 0; i < 100; i++)
{
if (<Main>o__SiteContainer0.<>p__Site1 == null)
{
<Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program), null, new CSharpArgumentInfo[] { new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
}
<Main>o__SiteContainer0.<>p__Site1.Target (<Main>o__SiteContainer0.<>p__Site1, d);
}
}
// Nested Types
[CompilerGenerated]
private static class <Main>o__SiteContainer0
{
// Fields
public static CallSite<Action<CallSite, object>> <>p__Site1;
}
}
可見在循環中,相同的操作做了一次緩存;但非循環環境下, 調用一次會緩存一次,猜測原因是,重復調用一個方法的次數不會太多,並且很 多情況准確查找起來比較困難。
(以上代碼在VS2010Beta1下測試通過)
URL: http://www.xianfen.net/