C#4.0新特性之(一)動態尋找
在大神Anders的領導下,C#這門語言也越來越快地朝著程式設計語言宇宙第一神器進化,C#4.0的新特徵都是圍繞“動態”(dynamic)的概念的,本文我們先來看看第一個新特性:動態尋找(Dynamic Lookup)。
1.初識dynamic
動態尋找允許動態(即在運行時)實現對某個對象的操作與物件類型的綁定,而不管這個對象是來自COM,IronPython,HTML DOM還是CLR的反射。你可以在程式中繞過編譯器的類型檢查,而把類型的匹配(lookup)丟給運行時去作。如果你需要對這樣的對象進行操作,則會用到一個全新的類型:dynamic
dynamic是一個和之前所有CTS支援的類型都很不一樣的類型,因為他不是object!確切的說,它會告知編譯器“請暫時別把我當成任何object!”。看上去這和過去的反射很類似,但是dynamic可以讓我們在代碼裡就可以直接實現對這個未知類型對象的操作,下面我們通過一個例子來說明dynamic帶來的便利。我的電腦上安裝了一種叫X雷的下載軟體,它提供了一些COM組件可供調用,在過去,我需要這樣來調用這個COM對象:
without Dynamic
private static void NoDynamicCall(String url = "http://www.sunhao.cc/temp/lgxz.wma")
{
Type thunderAgent;
object objThunderAgent;
object[] parameter = new object[14];
if (url != null && url.Length > 0)
{
thunderAgent = Type.GetTypeFromProgID("ThunderAgent.Agent");
objThunderAgent = Activator.CreateInstance(thunderAgent);
parameter[0] = url;//url
parameter[1] = "";
parameter[2] = "";
parameter[3] = "";
parameter[4] = url;//ref url
parameter[5] = -1;
parameter[6] = 0;
parameter[7] = -1;//threadCount
parameter[8] = "";//strCookie
parameter[9] = "";
parameter[10] = "";
parameter[11] = 1;
parameter[12] = "";
parameter[13] = -1;
thunderAgent.InvokeMember("AddTask5", BindingFlags.InvokeMethod, null, objThunderAgent, parameter);
object[] parm = new object[1] { 1 };
thunderAgent.InvokeMember("CommitTasks2", BindingFlags.InvokeMethod, null, objThunderAgent, parm);
}
}
這種通過Type.InvokeMemer在COM對象上調用方法實屬彆扭且無奈之舉,因為編譯器要先對方法的調用者進行類型綁定。不過現在有了dynamic類型,我們可以按照這樣的方式對上述com對象進行操作:
with Dynamic
Type agentType;
if (url != null && url.Length > 0)
{
agentType = Type.GetTypeFromProgID("ThunderAgent.Agent");
dynamic dAgent = Activator.CreateInstance(agentType);
dAgent.AddTask5(url, "", "", "", url, -1, 0, -1, "","", "", 1, "", -1);
dAgent.CommitTasks2(1);
}
這樣直接的調用方式要自然多了。不過你也許會問,既然這裡的dAgent的類型未知,而其AddTask5方法在編譯時間也完全不知道其存在性,那麼豈不是任何合法或者非法的調用都不會受到編譯器的監管,而把一切可能的危險留給了運行時?的確,編譯器只會檢查發生在CTS支援的各種類型上的調用,而dynamic在編譯時間還沒有被映射到任何一種CTS類型。
Tips 前面說的dynamic不是object句話若且唯若程式運行前是正確的,運行時dynamic會首先被聲明成為一個object,下面是IL描述的分配本地參數的stack上的資訊:
dynamic IL
.method private hidebysig static void DynamicCall([opt] string url) cil managed
{
.param [1] = "http://www.sunhao.cc/temp/lgxz.wma"
// Code size 420 (0x1a4)
.maxstack 17
.locals init ([0] class [mscorlib]System.Type agentType,
[1]
object
dAgent,
[2] bool CS$4$0000,
[3] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0001)
//以下省略N行
而編譯器對任何對發生在dynamic類型上的操作無能為力,他能做的唯一工作就是為運行時收集一些該dynamic對象的資訊,比如它上面的方法簽名。dynamic提供了訪問com對象的方便,但是由於它在一定程度上破壞了C#強型別的特性,同時也要求程式員對自己寫下的代碼完全負責,增加了debug的成本。所以說dynamic有風險,使用需謹慎。
2.DLR與自訂動態類型
Dynamic Language Runtime是.Net 4.0中一組全新的API。對於C#,DLR提供了Microsoft.CSharp.RuntimeBinder命名空間[1],它為C#提供了強大的運行時互操作(COM,Ironpython等)能力,DLR也有優秀的緩衝機制,對象一旦被成功綁定,CLR在下一次調用的時候就可以直接對確定類型的對象進行操作,而不必再通過DLR去lookup了。如果想在自己的代碼中實現一個動態類型對象,可以繼承DynamicObject[2]類,並實現自己的若干get和set方法。例如下面這個簡單的例子:
MyClass
public class MyClass:DynamicObject
{
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
result = binder.Name;
return true;
}
}
上述代碼在嘗試invoke某個方法的時候直接返回該方法的名字。於是下面的代碼將輸出方法名:
代碼
dynamic d = new MyClass();
Console.WriteLine(d.AnyMember());
3.dynamic的使用
由於dynamic本身也是一個類型(雖然只有編譯器認識它,運行時不認識它),而且dynamic實現了implicit和explicit運算子,理論上任何可以使用CLR的類型的地方都可以用dynamic。以下的代碼是合法的:
dynamic use case
dynamic d = (dynamic)2;
Action<dynamic> dAct = new Action<dynamic>((dynamic n) => { Console.Write(n.GetType()+": "+n); });
dAct(d);
但是dynamic也不是萬能的:
1).目前動態尋找不支援擴充方法的調用(可能在未來的版本的C#中會提供支援)。
2).匿名方法和Lambda運算式不能轉換為dynamic,也就是說dynamic d = x=>x;是不合法的,事實上lambda運算式也不能轉成object。一樣的道理,因為lambda運算式會在上下文環境下要麼被編譯器解釋成委託類型,要麼被解釋成運算式樹狀架構,但是如果上下文缺乏類型資訊,編譯器會confuse掉。
4.總結
dynamic是C#4.0的核心特徵,感覺上是C#這種強型別的語言多了一些動態語言的特徵,是對C#和.Net的一個完善。如本文開頭所說,作為一門程式設計語言,C#正在猛練北冥神功[3] ,這樣下去可能C#要和ms word一樣成為居家旅行殺人越貨必備的武器了。
5.引用
[1] http://msdn.microsoft.com/en-us/library/microsoft.csharp.runtimebinder(VS.100).aspx
[2] http://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject(VS.100).aspx
[3] http://baike.baidu.com/view/146278.htm
Author:Freesc Huang @ CNBlogs