既然要動態就動個痛快 - ExpandoObject
0 |
動態語言是C# 4.0的重要特色之一,dynamic關鍵字的出現,簡化了以往用Reflection大費周章才能做到的物件屬性(Property)及方法(Method)動態存取。
用個簡單的例子示範:
using System;
using Microsoft.CSharp.RuntimeBinder;
using System.Reflection;
namespace DynamicLab
{
//定義兩個類別,有一個相同的Property(Name)
//但各有不同的Field(Age vs Score)
class Boo
{
public string Name { get; set; }
public int Age;
}
class Foo
{
public string Name { get; set; }
public int Score;
}
class Program
{
static void Main(string[] args)
{
//分別建立兩個物件
Boo boo = new Boo()
{ Name = "Jeffrey", Age = 25 };
Foo foo = new Foo()
{ Name = "Darkthread", Score = 32767 };
//使用Reflection存取Name屬性及欄位Score
ShowAnythingByReflection(boo);
ShowAnythingByReflection(foo);
//改用dynamic來處理做為對照
ShowAnythingByDynamic(boo);
ShowAnythingByDynamic(foo);
Console.Read();
}
#region Reflection
//Reflection法
static void ShowAnythingByReflection(object obj)
{
Console.WriteLine("Type: " + obj.GetType().ToString());
Console.WriteLine("Name=" + TryGetProperty(obj, "Name"));
try
{
Console.WriteLine("Score=" + TryGetField(obj, "Score"));
}
catch (Exception ex)
{
Console.WriteLine("Error:" + ex.Message);
}
}
//處理Property與處理Field做法不同,故寫成兩個Method
static object TryGetProperty(object obj, string propName)
{
Type objType = obj.GetType();
PropertyInfo pi = objType.GetProperty(propName);
if (pi == null)
throw new ApplicationException(
"Property " + propName + " not found!");
else
return pi.GetValue(obj, null);
}
static object TryGetField(object obj, string fieldName)
{
Type objType = obj.GetType();
FieldInfo fi = objType.GetField(fieldName);
if (fi == null)
throw new ApplicationException(
"Field " + fieldName + " not found!");
else
return fi.GetValue(obj);
}
#endregion
#region Dynamic
//dynamic比較乾脆,直接串上.propName或.fieldName就對了
//編譯一定會過(因為不檢查),若Property或Field不存在,
//則丟出RuntimeBinderException
static void ShowAnythingByDynamic(dynamic dyna)
{
Console.WriteLine("Type: " + dyna.GetType().ToString());
try
{
Console.WriteLine("Name=" + dyna.Name);
}
catch (RuntimeBinderException ex)
{
Console.WriteLine("Error:" + ex.Message);
}
try
{
Console.WriteLine("Score=" + dyna.Score);
}
catch (RuntimeBinderException ex)
{
Console.WriteLine("Error:" + ex.Message);
}
}
#endregion
}
}
執行結果:
Type: DynamicLab.Boo
Name=Jeffrey
Error:Field Score not found!
Type: DynamicLab.Foo
Name=Darkthread
Score=32767
Type: DynamicLab.Boo
Name=Jeffrey
Error:'DynamicLab.Boo' does not contain a definition for 'Score'
Type: DynamicLab.Foo
Name=Darkthread
Score=32767
面對傳入可能是Boo或Foo的類別,試圖存取其Name屬性及Score欄位,用dynamic寫是否比用Reflection乾淨俐落多了?
不過,在以上的例子中,Boo、Foo都是事先定義好的類別,並沒法真的做到像Javascript的變形蟲玩法,在Runtime才動態決定掛上哪些Property、Method:
var boo = {};
boo["Name"] = "Jeffrey";
alert(boo.Name);
boo["Show"] = function(m) { alert(m); };
boo.Show("Hello, World!");
會這樣提,莫非.NET 4.0現在也辦得到?
Yes! Use the ExpandoObject, Luke!
using System;
using Microsoft.CSharp.RuntimeBinder;
using System.Reflection;
using System.Dynamic;
using System.Linq;
using System.Collections.Generic;
namespace DynamicLab
{
class Program
{
static void Main(string[] args)
{
//將boo建立成ExpandoObject
dynamic boo = new ExpandoObject();
//直接寫boo.Name加上新的Property
boo.Name = "Jeffrey";
Console.WriteLine(boo.Name);
//ExpandoObject可轉型成IDictionary<stirng, object>
//讓我們可以用boo[someVariable]的方式加上新的成員
//在要動態決定物件成員名稱的場合很好用
IDictionary<string, object> booDict =
boo as IDictionary<string, object>;
//掛上Show方法
booDict["Show"] = (Action<string>)
(m => { Console.WriteLine(m); });
boo.Show("Hello, World!");
Console.Read();
}
}
}
很酷吧!
動態語言寫起來很簡潔,但卻失去了編譯期語法檢查的保護,導致在執行期可能發生料想不到的錯誤,該不該用見仁見智,但每一種語言特性一定有其擅長的場合,要怎麼巧妙運用就看開發者的功力。至少,C# 4.0提供了十八般武器,要用小李飛刀、魚腸劍還是青龍偃月刀,就是大家的自由囉~~~
(話說回來,選錯兵器上場,被人砍到仆街也是常見的結果,擁用選擇的權利是好事,但也務必謹慎為之)
【延伸閱讀】
Comments
Be the first to post a comment