利用ExpandoObject類別,在執行期動態加入和移除成員

在.NET Framework 4.0後出現的System.Dynamic,讓C#漸漸邁向語法鬆散化的不歸路,這次要練習的是ExpandoObject類別,有了ExpandoObject,我們再也不用羨慕JavaScript宣告一個物件的寫法,也不必再受限於匿名型別(Anonymous Types)只能夠承載資料型別(Data Types)或物件,但是沒有辦法承載方法(Methods)。因此,ExpandoObject類別在某些資料結構比較複雜的應用下,會讓你的程式碼變得更精簡!

一段JavaScript的Expando Property會長的像這樣,語法夠鬆散了吧!

var obj = { Prop1: '' };
obj.Prop1 = 'foo';  //Prop1 is NOT an Expando Property
obj.Prop2 = 'bar';  //Prop2 is an Expando Property

不過在這邊也要先幫匿名型別(Anonymous Types)平反一下,當你選擇使用ExpandoObject後,因為所有的驗證(錯誤)都是在執行期運行了,所以VisualStudio引以為豪的IntelliSense瞬間變的毫無用武之地,也就是說你的觀念要變得很好,你才會知道自己正在寫什麼。反觀Anonymous Types,你在定義它完成後,VisualStudio的IntelliSense可是運作的非常良好呢!

開始ExpandoObject的初體驗

ExpandoObject的語法超簡單的,如果你說看不懂我也沒辦法了。要注意的是,一開始的變數前面要宣告dynamic,這表示叫編譯器不要檢查,一切到Runtime時期再說。此外,ExpandoObject是不可以被Instance的。此外,不要試圖去呼叫一個不存在的成員,不然你會在Runtime時期得到Exception。

static void Main(string[] args)
{
	dynamic oEmployee = new System.Dynamic.ExpandoObject();
	oEmployee.cName = "John";
	
	//不可以被實例化
	//dynamic oPeople = new oEmployee();

	//不可以亂呼叫不存在的屬性,會觸發Exception
	//Console.WriteLine(oEmployee.cTel);

	Console.WriteLine(oEmployee.cName);
	Console.Read();
}

來個複雜一點的資料型別

接著我們試著把某一些屬性,加載一些較複雜的類別物件List(),觀察看看是否可以正確的運行。其中的Class Motos我用偷懶的使用巢狀類别(Nested Type)。在驗證輸出,我使用Newtonsoft.Json.JsonConvert.SerializeObject來幫輸出成JSON,因為我想要觀察ExpandoObject在複雜的操作情況下,是否可以被正確的序列化成字串?

class Program
{
	//Nested Type
	class Motos
	{
		public string cID { get; set; }
		public string cType { get; set; }
	}
	//Main
	static void Main(string[] args)
	{
		dynamic oEmployee = new System.Dynamic.ExpandoObject();
		oEmployee.cName = "John";
		oEmployee.oMotos = new System.Collections.Generic.List<Motos>();
		oEmployee.oMotos.Add(new Motos() { cID = "AB-1234", cType = "汽車" });
		oEmployee.oMotos.Add(new Motos() { cID = "CD-5678", cType = "機車" });
		//吐出JSON
		Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(oEmployee));
		Console.Read();
	}
}

結果是可以的!真好!

{
	"cName":"John",
	"oMotos":[
		{
			"cID":"AB-1234",
			"cType":"汽車"
		},
		{
			"cID":"CD-5678",
			"cType":"機車"
		}
	]
}

開始加入方法(Methods)

接下來要做一些匿名型別Anonymous Types辦不到的事情了,開始加入可以被操作的方法成員!當然是採用我們喜愛的System.Action來進行委派啦!

static void Main(string[] args)
{
	dynamic oEmployee = new System.Dynamic.ExpandoObject();
	oEmployee.cName = "John";
	oEmployee.iSalary = 22000;
	oEmployee.AddSalary = (System.Action<int>)((iMoney) => { oEmployee.iSalary += iMoney; });
	//幫John加薪
	oEmployee.AddSalary(1000);
	Console.WriteLine(string.Format("{0}現在的薪資是{1}元。", oEmployee.cName, oEmployee.iSalary));
	Console.Read();
}

很明顯的,John進入巨匠後薪資從22K升級到23K。

John現在的薪資是23000元。

方法成員不可以被序列化

在上面的例子中,我們使用了System.Action來實作方法成員,但如果我們這個物件最終,與前前例子一樣,在AddSalary()被操作後我們想要直接丟成JSON,這可行嗎?答案是否定的,因為方法不可能被序列化成資料啊!這時候我們就要請出System.Dynamic.ExpandoObject的原型,其實它就是一個實作System.Collections.Generic.IDictionary<TKey, TValue>的類別。

static void Main(string[] args)
{
	dynamic oEmployee = new System.Dynamic.ExpandoObject();
	oEmployee.cName = "John";
	oEmployee.iSalary = 22000;
	oEmployee.AddSalary = (System.Action<int>)((iMoney) => { oEmployee.iSalary += iMoney; });
	//幫John加薪
	oEmployee.AddSalary(1000);
	//轉型回IDictionary<TKey, TValue>
	var oTemp = (System.Collections.Generic.IDictionary<string, object>)oEmployee;
	//移除不可能被序列化的成員
	oTemp.Remove("AddSalary");
	Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(oTemp));
	Console.Read();
}

這下John的23K薪資可以被大剌剌的公告在網路上了!

{
	"cName":"John",
	"iSalary":23000
}
C# System.Dynamic.ExpandoObject AnonymousTypes JavaScript ExpandoProperty