ExpandoObject 能夠讓我們在執行時動態增加和移除物件屬性、方法,就像是增強版的 Dictionary (ExpandoObject 本身有實作 IDictionary 介面),在處理動態資料時非常好用。
如果還沒有賦值就呼叫會出現下面的錯誤:
註:呼叫不存在的屬性在編譯時並不會出現錯誤,直到執行時才會拋出錯誤
還好 ExpandoObject 有實作 IDictionary<String, Object> 介面,所以我們可以像是使用 Dictionary 一樣的方式使用 ExpandoObject 來取出或放入屬性,兩者也可以交叉使用,非常靈活
參考資料:
Microsoft.Learn - ExpandoObject Class
Microsoft.Learn - Action<T> Delegate
Microsoft.Learn - Func<T,TResult> Delegate
Microsoft.Learn - INotifyPropertyChanged Interface
Microsoft.Learn - IDictionary<TKey,TValue> Interface
建立物件
建立 ExpandoObject 需要使用 dynamic 關鍵字宣告
dynamic obj = new ExpandoObject();
增加屬性
在物件後面加上點 (.) 後可以直接建立並使用動態屬性,例如 obj.A 就是直接使用 A 屬性,會自動判斷型別,不過都需要先賦值才能使用
obj.PropertyInt = 123;
Console.WriteLine(obj.PropertyInt); // 123
Console.WriteLine(obj.PropertyInt.GetType()); // System.Int32
obj.PropertyString = "PropertyString";
Console.WriteLine(obj.PropertyString); // PropertyString
Console.WriteLine(obj.PropertyString.GetType()); // System.String
obj.PropertyDateTime = DateTime.Now;
Console.WriteLine(obj.PropertyDateTime); // 2022/12/16 下午 23:59:59
Console.WriteLine(obj.PropertyDateTime.GetType()); // System.DateTime
obj.PropertyDouble = 123.456;
Console.WriteLine(obj.PropertyDouble); // 123.456
Console.WriteLine(obj.PropertyDouble.GetType()); // System.Double
如果還沒有賦值就呼叫會出現下面的錯誤:
Console.WriteLine(obj.Property2); // 拋出錯誤
/*
Unhandled exception. Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'System.Dynamic.ExpandoObject' does not contain a definition for 'Property2'
at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at Program.<Main>$(String[] args) in C:\Users\ruyut\Documents\RiderProjects\test\2022\ConsoleAppDotNet6Test\ConsoleAppDotNet6Test\Program.cs:line 27
*/
註:呼叫不存在的屬性在編譯時並不會出現錯誤,直到執行時才會拋出錯誤
動態使用屬性
上面使用的都需要和一般使用屬性的方式一樣,使用點 (.) 再加上屬性名稱,不過這樣就無法動態的呼叫了。還好 ExpandoObject 有實作 IDictionary<String, Object> 介面,所以我們可以像是使用 Dictionary 一樣的方式使用 ExpandoObject 來取出或放入屬性,兩者也可以交叉使用,非常靈活
dynamic obj = new ExpandoObject();
var dictionary = obj as IDictionary<String, Object>;
dictionary["Name"] = "Ruyut";
dictionary.Add("Id", 1);
Console.WriteLine(obj.Name); // Ruyut
Console.WriteLine(obj.Id); // 1
obj.Id = 2;
Console.WriteLine(dictionary["Id"]); // 2
列舉出所有屬性
剛剛提到過有實作 IDictionary ,所以要查看所有屬性和內容非常的容易:
foreach (var item in obj as IDictionary<String, Object>)
{
Console.WriteLine($"{item.Key} : {item.Value}");
}
// Name : Ruyut
// Id : 2
刪除屬性
刪除屬性需要先將 dynamic 轉換為 IDictionary ,再使用 Remove 方法移除
var dictionary = obj as IDictionary<String, Object>;
dictionary.Remove("Id");
屬性變更通知
因為 ExpandoObject 有實作 INotifyPropertyChanged 介面,我們可以使用下面的方式將有變更的屬性和新的內容顯示出來
dynamic obj = new ExpandoObject();
var dictionary = obj as IDictionary<String, Object>;
((INotifyPropertyChanged)obj).PropertyChanged += (sender, e) =>
{
string notify = $"{e.PropertyName} changed";
if (dictionary.ContainsKey(e.PropertyName)) // 檢查屬性沒有被移除
{
notify += $", New value: {dictionary[e.PropertyName]}";
}
Console.WriteLine(notify);
};
動態增加方法
這裡直接示範使用委派(Delegate)建立沒有回傳值和有回傳值的方法
// 顯示訊息 (沒有回傳值)
obj.SayHello = new Action<string>(name => Console.WriteLine($"Hello {name}!"));
obj.SayHello("Ruyut");
// 計算 A + B (有回傳值)
obj.Add = new Func<int, int, int>((a, b) => a + b);
Console.WriteLine(obj.Add(1, 2));
參考資料:
Microsoft.Learn - ExpandoObject Class
Microsoft.Learn - Action<T> Delegate
Microsoft.Learn - Func<T,TResult> Delegate
Microsoft.Learn - INotifyPropertyChanged Interface
Microsoft.Learn - IDictionary<TKey,TValue> Interface
留言
張貼留言
如果有任何問題、建議、想說的話或文章題目推薦,都歡迎留言或來信: a@ruyut.com