[.NET] 動態語言能力:自製 dynamic 物件 (1): ExpandoObject 物件

dynamic 型別,這個由 C# 4.0 (.NET Framework 4.0) 開始,打開了 C# 這個編譯式語言的動態之路,它的執行時期決議機制,使得開發人員能在只知道它的成員的情況下即可呼叫使用,它是動態語言執行期 (Dynamic Language Runtime, DLR) 的一部份,相對於 CLR (Common Language Runtime) 需要編譯才會產生相關的程式結構,在 DLR 的機制下,程式只在執行期才會解析相關的程式結構,並產生執行器所需要的資訊,而由於 DLR 是在 CLR 之上,使得 DLR 平台可完全相容於 CLR,並且與 CLR-compliant 的平台與程式語言相互使用。

dynamic 型別,這個由 C# 4.0 (.NET Framework 4.0) 開始,打開了 C# 這個編譯式語言的動態之路,它的執行時期決議機制,使得開發人員能在只知道它的成員的情況下即可呼叫使用,它是動態語言執行期 (Dynamic Language Runtime, DLR) 的一部份,相對於 CLR (Common Language Runtime) 需要編譯才會產生相關的程式結構,在 DLR 的機制下,程式只在執行期才會解析相關的程式結構,並產生執行器所需要的資訊,而由於 DLR 是在 CLR 之上,使得 DLR 平台可完全相容於 CLR,並且與 CLR-compliant 的平台與程式語言相互使用。

dynamic 利用執行時期決議機制,讓 C# 開發人員在存取複雜的 COM 元件或 Office COM 時更加輕鬆,而有些開發技巧也能運用 dynamic 來實作,但麻煩的是 dynamic 型別不受編譯器保護,也就是說 dynamic 無法享有 Intellisense 的好處,也沒辦法在編譯時期由編譯器做檢查,這也會導致成員不存在的問題。不過若是常寫動態語言 (ex: JavaScript) 的開發人員可能就不會太在意這一點,反正執行錯誤時執行器會丟錯誤出來 (也就是例外)。

而在類別物件充斥的時代,有很多事情都會習慣用物件解決,以避免用字串交換或是要做序列化的程序,像微軟發展出 Tuple<T> 類別,讓不需要特地宣告的物件能省去宣告類別這件事 (例如 ViewModel 或只有特定事件會用的資料結構),只是 Tuple<T> 那個 Item1, Item2 的屬性名稱總是讓人有隔閡感,這時以動態語言的方式來實作可能會比較容易。

首先,我們先用簡單的 ExpandoObject 來實作這個特性,其實黑暗大已經有一篇文章說明了:http://blog.darkthread.net/post-2011-06-10-expandoobject.aspx,ExpandoObject 屬於 System.Dynamic 命名空間的一部份,位於 System.Core.dll 內,它實作了數個動態物件所需要的介面,讓開發人員可以用 dynamic 直接加入或是轉型成 IDictionary<string, object> 的方式來新增成員,例如:

static dynamic CreateDynamicObject()
{
    dynamic d = new ExpandoObject();

    // assign property
    d.Prop1 = 1;
    d.Prop2 = 2;
    d.Prop3 = 3;

    // assign void method.
    d.InvokeVoid = new Action(() => Console.WriteLine("InvokeVoid."));

    // assign returnable method.
    d.InvokeAdd = new Func<int, int, int>((a, b) => a + b);

    return d;
}

和下面這段程式碼有相同的功能:

static dynamic CreateDynamicObject2()
{
    dynamic d = new ExpandoObject();
    IDictionary<string, object> item = d as IDictionary<string, object>;

    // assign property
    item.Add("Prop1", 1);
    item.Add("Prop2", 2);
    item.Add("Prop3", 3);

    // assign void method.
    item.Add("InvokeVoid", new Action(() => Console.WriteLine("InvokeVoid.")));

    // assign returnable method.
    item.Add("InvokeAdd", new Func<int, int, int>((a, b) => a + b));

    return d;
}

呼叫方法都是一樣的:

static void Main(string[] args)
{
    dynamic d = CreateDynamicObject2();

    Console.WriteLine("Prop1: {0}", d.Prop1);
    Console.WriteLine("Prop2: {0}", d.Prop2);
    Console.WriteLine("Prop3: {0}", d.Prop3);

    d.InvokeVoid();
    var e = d.InvokeAdd(1, 2);

    Console.WriteLine("e={0}", e);

    Console.Read();
}

Sample Code: https://gist.github.com/anonymous/8178845