2010年2月11日 星期四

C# 4.0 : dynamic language (1)

在 C# 4.0,增加了動態語言的特性。

簡單地說,使用 dynamic 宣告出來的變數,在編譯器編譯時期會被省略不編譯,直到執行期到動態地解譯所使用的 member field,  member function 或 function。

舉例來說,下面的程式會發生編譯錯誤。原因是 object 這種類別沒有 Name 的屬性。

例1
   1: class C1
   2: {
   3:   public string Name { get; set; }
   4: }
   5: class Program
   6: {
   7:   static void Main(string[] args)
   8:   {
   9:     C1 c1 = new C1() { Name = "a" };
  10:     WriteName(c1);
  11:   }
  12:   public static void WriteName(object o)
  13:   {
  14:     Console.WriteLine(o.Name);
  15:   }
  16: }

想要讓這個程式碼 compile 通過當然是很容易的事,只需要將 WriteName 的參數由 object 型別改成 C1 型別即可。

以下這一段程式,想要寫一個能印出物件的 Name 屬性的共用程式 WriteName。

例2
   1: class C1
   2:   {
   3:     public string Name { get; set; }
   4:   }
   5:   class C2
   6:   {
   7:     public string Name { get; set; }
   8:     public int Id { get; set; }
   9:   }
  10:   class Program
  11:   {
  12:     static void Main(string[] args)
  13:     {
  14:       C1 c1 = new C1() { Name = "a" };
  15:       C2 c2 = new C2() { Name = "b", Id = 1 };
  16:       WriteName(c1);
  17:       WriteName(c2);
  18:     }
  19:     public static void WriteName(C_What o)
  20:     {
  21:       Console.WriteLine(o.Name);
  22:     }
  23:   }

WriteName 的參數1,應該宣告 C1 還是 C2 呢?都不行,靜態語言是不會過的。精通物件導向的我們,當然知道此時最好使用 interface 來解決問題。如下。

例3
   1: interface IName
   2:   {
   3:     string Name { get; set; }
   4:   }
   5:   class C1 : IName
   6:   {
   7:     public string Name { get; set; }
   8:   }
   9:   class C2 : IName
  10:   {
  11:     public string Name { get; set; }
  12:     public int Id { get; set; }
  13:   }
  14:   class Program
  15:   {
  16:     static void Main(string[] args)
  17:     {
  18:       C1 c1 = new C1() { Name = "a" };
  19:       C2 c2 = new C2() { Name = "b", Id = 1 };
  20:       WriteName(c1);
  21:       WriteName(c2);
  22:     }
  23:     public static void WriteName(IName o)
  24:     {
  25:       Console.WriteLine(o.Name);
  26:     }
  27:   }

這樣一來,解決了 C1, C2 的問題。「只」需要實作 IName 的介面,就可以使用 WriteName 這個函數了。

問題就在 IName 是一個自訂的介面,不是通用的介面。別人寫好的類別,絕對不會知道及實作我們自訂的 IName 介面。如果我們的WriteName 想要通吃呢?下面的程式就是使用 Reflection 機制寫出來的 solution

例4
   1: class C1
   2:   {
   3:     public string Name { get; set; }
   4:   }
   5:   class C2
   6:   {
   7:     public string Name { get; set; }
   8:     public int Id { get; set; }
   9:   }
  10:   class Program
  11:   {
  12:     static void Main(string[] args)
  13:     {
  14:       C1 c1 = new C1() { Name = "a" };
  15:       C2 c2 = new C2() { Name = "b", Id = 1 };
  16:       WriteName(c1);
  17:       WriteName(c2);
  18:     }
  19:     public static void WriteName(object o)
  20:     {
  21:       Type type = o.GetType();
  22:       string str = (string)type.GetProperty("Name").GetValue(o, null);
  23:       Console.WriteLine(str);
  24:     }
  25:   }

這個方式,一律使用 object 來接參數,再使用 Reflection 的機制來動態的取得 Name 的 property,並當成字串輸出到 Console。棒吧!

可是,這段程式有點難寫耶!如果一次要取得三個property,並執行傳入參教的某個 function,就累翻了。

最後,則是使用 dynamic 的宣告,將例3的 C_What 改成 dynamic 即完成了

例5

   1: class C1
   2:   {
   3:     public string Name { get; set; }
   4:   }
   5:   class C2
   6:   {
   7:     public string Name { get; set; }
   8:     public int Id { get; set; }
   9:   }
  10:   class Program
  11:   {
  12:     static void Main(string[] args)
  13:     {
  14:       C1 c1 = new C1() { Name = "a" };
  15:       C2 c2 = new C2() { Name = "b", Id = 1 };
  16:       WriteName(c1);
  17:       WriteName(c2);
  18:     }
  19:     public static void WriteName(dynamic o)
  20:     {
  21:       Console.WriteLine(o.Name);
  22:     }
  23:   }

例5 的語法與例3幾乎完全一樣,但事實上走的是例4的執行時間Reflection 機制。大大地節省了程式碼的撰寫。

由執行時期才決定資訊的機制,就是動態語言的特性。

沒有留言:

Share with Facebook