動態語言是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

Post a comment