[.NET] 善用匿名型別來組裝非結構的資料

在 .NET 平台寫程式寫久了,一定會覺得程式中的資料一定得物件化,而且要有明確的成員來規範,沒錯,這是我們告訴大家寫程式的基本要求,能做強型別的就一定要做強型別,不然光是轉型 (type casting) 這件事就能搞死一堆人了...

在 .NET 平台寫程式寫久了,一定會覺得程式中的資料一定得物件化,而且要有明確的成員來規範,沒錯,這是我們告訴大家寫程式的基本要求,能做強型別的就一定要做強型別,不然光是轉型 (type casting) 這件事就能搞死一堆人了。

然而,卻不是所有的情況都得用強型別,尤其是當資料只會用一次,在一個地方使用,或是想要更具彈性時,宣告成強型別會大大的縮減它的彈性,但我們又不想要使用弱型別的物件 (ex: ArrayList, DataTable, DataRow, …),這時 .NET 的匿名型別 (anonymous type) 就很有用了,它的型別資訊是由編譯器在編譯過程中動態給予的,開發人員只要用很簡單的 new 就能自由創造匿名型別。

不過,創造歸創造,我們如何在程式中使用這些匿名物件呢?因為匿名物件有幾個特性:

  • 匿名物件擁有自己的型別,無法轉型成其他型別,只有 object 除外。
  • 匿名物件只能存在於參數,無法跨越出函數之外,除非回傳的是 object。
  • 匿名物件宣告後,只能使用 dynamic 或 Reflection 來存取。

掌握了這幾個特性後,我們可以做一些設計。

例如下列程式碼是一個將兩位數相加的程式,但參數並沒有給予要相加的兩個值,我們要以匿名物件來宣告:


public static object AddWithObject(object Params)
{
    PropertyInfo[] ps = Params.GetType().GetProperties();

    object result =
        Convert.ToInt32(ps[0].GetValue(Params, null)) +
        Convert.ToInt32(ps[1].GetValue(Params, null));

    return result;
}

當 Params 傳入的是匿名型別時,我們因為無法使用強型別的方式來存取,因此我們只能使用 Reflection 將 Params 的匿名型別內的屬性反射出來,才能得到我們需要的資訊。用戶端程式可以這樣撰寫:


var o2 = AddWithObject(new
            {
                X = 200,
                Y = 392
            });

這樣做也許有人會覺得麻煩,我們可以利用 .NET 4.0 提供的 dynamic 算符來簡化,dynamic 算符會要求 CLR 在執行時期才繫結型別資訊,也就是說我們可以不用 Reflection 即可存取匿名物件內的成員,我們可以把 AddWithObject() 改變寫法使用 dynamic:


public static dynamic AddWithDynamic(dynamic Params)
{
    return Params.X + Params.Y;
}

用戶端程式可以這樣寫:


var o1 = AddWithDynamic(new
{
    X = 100,
    Y = 140
});

結果和 AddWithObject() 是相同的。

不過我個人是推薦 AddWithObject 的寫法,因為它比較彈性,我們還可以先一步偵知指定的屬性是否存在,而 dynamic 的寫法無法偵知指定的屬性是否存在,例如我們把 AddWithDynamic 中的參數 Y 拿掉,在執行時期 CLR 會丟給你一個 RuntimeBinderException,因為找不到 Y 屬性。

但如果已經確定匿名型別一定會傳入哪些屬性的話,使用 dynamic 可以簡化不少程式。

完整範例程式:


namespace AnonymousTypeSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var o1 = AddWithDynamic(new
            {
                X = 100
                //Y = 140
            });

            var o2 = AddWithObject(new
            {
                X = 200,
                Y = 392
            });

            Console.WriteLine("Add1: {0}", o1);
            Console.WriteLine("Add2: {0}", o2);

            Console.ReadLine();
        }

        public static dynamic AddWithDynamic(dynamic Params)
        {
            return Params.X + Params.Y;
        }

        public static object AddWithObject(object Params)
        {
            PropertyInfo[] ps = Params.GetType().GetProperties();

            object result =
                Convert.ToInt32(ps[0].GetValue(Params, null)) +
                Convert.ToInt32(ps[1].GetValue(Params, null));

            return result;
        }
    }
}

Reference: http://msdn.microsoft.com/zh-tw/library/bb397696.aspx