C# 4.0 New Feature : Dynamic Programming

看著一個程式語言的誕生,然後逐步追隨其成長,是一件相當有趣的事,特別是該程式語言一直都處於主流語言的時候,很可惜的,這種機會並不常有,C#是在我程式生涯中,唯一一個從其出生即跟隨至今的程式語言。在C#誕生之初,也是Anders Hejlsberg離開Borland之後的數年後

 

C# 4.0 New Feature : Dynamic Programming
 
/黃忠成
 
 
 
 
C# 10
 
 
 
     看著一個程式語言的誕生,然後逐步追隨其成長,是一件相當有趣的事,特別是該程式語言一直都處於主流語言的時候,很可惜的,這種機會並不常有,C#是在我程式生涯中,唯一一個從其出生即跟隨至今的程式語言。
 
    在C#誕生之初,也是Anders Hejlsberg離開Borland之後的數年後,對於一個老Delphi設計師而言,對於C#的感情因素遠比其語言本質來得重,AndersDelphi的主要掌舵者,他成功的將Delphi帶入一個RAD工具前所未進過的殿堂,在他離開Borland而建立C#後,我們在C#上看到了Delphi的影子,PME的設計,Refection的進化,諸多設計都可見到Delphi的背影。當然,我並不是說C#是抄襲Delphi,畢竟當時也有人說C#Java很像,C#C++很像,這些其實都不重要,重要的是,C#是擷取了許多語言的優點而成的,對於程式設計師而言,這點就足夠了。
 
     C# 1.0隨著.NET Framework一起問市,創始者AndersC# 1.0定位為Managed Version,這是繼Java之後的一個中繼平台高階語言,與Java相同,.NET Framework中的CLR負責來執行由C# 編譯器所產生的IL Code,藉此將機器語言及中介語言切開,如你所見,優點是C#可以與VB.NET互通,在理論上,還能針對不同的CPU進行最佳化,這是傳統直接編譯成機器碼的編譯器所得不到的好處。
 
   C# 2.0進入了泛型(Generic)時代,在這個版本中,程式設計師可以僅寫一個Stack<T>,然後支援所有型別,而不用受object原型的拖累,泛型的優點在於其一開始就已定型,因此假設將一變數宣告為Stack<int>,那麼之後就不能夠將string元素填入,既可脫開單一型別必須撰寫對應支援的Stack負擔,還得到了使用object所得不到的編譯時期驗證。
 
      C# 3.0開始,進入了一個極少數程式語言到過的境界,Anders大膽的於C#中加入了LINQ(Language Integrated Query),將查詢語句硬是擺進了程式語言中,事實證明LINQ是個相當棒的發明。
 
     C# 4.0,一個嶄新的世代,Anders再次顯露了其大膽的個性,將原本受盡眾人詬病的Un-Type Programming帶入了C#,讓原本以Typed Programming掛帥的C#,頓時多了另一個Dynamic Language的稱號,雖然!人們對於Un-Type Programming的疑慮仍未解除,但Dynamic Language機制的加入,無疑的開啟了C#另一條進步的路。
 
     10年,對於一個程式語言來說,並不是一段很長的時間,C#於這10年間的轉變算是相當的快速,且每次的轉變,都會讓人覺得有疑慮,可是最後都會不禁對其大膽的嘗試感到讚賞,當然!前題是你得是實務掛帥的人,因為只要從理論上及原則上來看,由C# 3.0開始,其軸心思想即不在舊有程式語言抱持的一貫原則上了,而是在如何加快設計師的生產力上。
 
 
嚴謹(Typed Programming) VS 鬆散(Dynamic Programming)
 
 
     C# 3.0之前,她是一個嚴謹的程式語言,也可稱為是Typed Programming,意思是,當你將一個變數宣告為int,那麼你就不能把一個字串賦值給它,這樣的做法有很多好處,其中之一就是編譯器會於編譯時期即告知設計者所犯下的型別錯誤,這大幅的減少了因型別錯誤而產生的BUG,同時間接的養成了C#設計師對於型別的敏感度。
 
     C# 3.0開始,因為LINQ的加入,var關鍵字出現了,其出現的原因是LINQ運算式的回傳值,常常是設計師難以快速推估出來的,以下面的例子來說吧:

 

static void Main(string[] args)
{
            List<Person> list = new List<Person>() {
                new Person(){ID="001",Name="code6421",Age = 18},
                new Person(){ID="002",Name="tom",Age = 18},
                new Person(){ID="003",Name="mary",Age = 18}};
 
            List<Addresses> alist = new List<Addresses>()
            {
                new Addresses(){ID="001",Address="Taipen"},
                new Addresses(){ID="002",Address="Tainan"},
                new Addresses(){ID="003",Address="US"}
            };
 
            var result = from s1 in list
                    join s2 in alist on s1.ID equals s2.ID into p
                    select new { Name = s1.Name, Addresses = p };
            foreach (var item in result)
            {
                Console.WriteLine(item.Name);
                Console.WriteLine("-----------------");
                foreach (var addr in item.Addresses)
                    Console.WriteLine(addr.Address);
                Console.WriteLine("-----------------");
            }
            Console.ReadLine();
 }
如果沒有var,那麼就得寫成下面這樣:

 

static void Main(string[] args)
{
     List<Person> list = new List<Person>() {
        new Person(){ID="001",Name="code6421",Age = 18},
        new Person(){ID="002",Name="tom",Age = 18},
        new Person(){ID="003",Name="mary",Age = 18}};
 
     List<Addresses> alist = new List<Addresses>()
     {
         new Addresses(){ID="001",Address="Taipen"},
         new Addresses(){ID="002",Address="Tainan"},
         new Addresses(){ID="003",Address="US"}
     };
 
     IEnumerable<PersonJoinResult> result = from s1 in list
              join s2 in alist on s1.ID equals s2.ID into p
              select new PersonJoinResult{ Name = s1.Name, Addresses = p };
     foreach (PersonJoinResult item in result)
     {
          Console.WriteLine(item.Name);
          Console.WriteLine("-----------------");
          foreach (Addresses addr in item.Addresses)
                Console.WriteLine(addr.Address);
          Console.WriteLine("-----------------");
     }
     Console.ReadLine();
   }
}
 
public class PersonJoinResult
{
    public string Name { get; set; }
    public IEnumerable<Addresses> Addresses { get; set; }
}
很明顯的,即使var背負上不定型別的臭名,但其對於程式碼的簡化有著莫大的幫助,更何況,var於右賦值完成時,即轉變為具型別的形態,因此精確的說,var還是具型的設計。
 C#4.0中添加了一個新成員:dynamic,與var這種乍看不具型但實際具型的設計不同,dynamic是完全不具型的設計,它的型別是執行時期時決定的,所以下面的例子是可以通過編譯的。

 

class Program
{
    static void Main(string[] args)
    {
      dynamic p = new Person();
      p.Hello();
    }
}
 
public class Person
{
    public string ID { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}
Person並沒有一個方法稱為Hello,原本於Typed Programming的原則下,這個程式是無法通過編譯的,但C# 4.0dynamic型別卻可以讓這段程式通過編譯,將一變數宣告為dynamic型別,即是告訴編譯器【我知道我在做什麼,你別管了!】。
有趣的還有下面這段程式碼

 

static void Main(string[] args)
{
    dynamic p = 15;
    Console.WriteLine(p);
}
此時p的型別是什麼呢?答案是object,事實上當你將某一變數宣告為dynamic時,C#編譯器就會把它視為是object,然後以Lambda Expression的方式來處理你對此變數的函式呼叫、屬性存取、Index存取等等,即使我們明確的知道p應該是Integer,但由於已經標為dynamic,自然的也就不能再經由具型來處理了。這個例子告訴我們,dynamic沒那麼聰明,不會因為你設了一個簡單值給他,編譯器便會很聰明的使用具型方式處理,一切還是循不具型方式來。因此,如非必要,否則別把原本可具型的東西宣告為dynamic,那對程式的效能及可讀性是莫大的傷害。但也不必因此而不用它,畢竟利刃是給會用的人使用的。
 
var dynamic
 
     基本上,var是一種明確右賦值決議的型別,但dynamic是完全不具型的,要知道兩者間的差異性,我們可以用以下的例子來看:

 

static void Main(string[] args)
{
   dynamic p = 15;
   var s = 15;
  Console.WriteLine(p);
   Console.WriteLine(s);
}
透過Reflector,會成為下面這樣:

 

private static void Main(string[] args)
{
    object p = 15;
    int s = 15;
    if (<Main>o__SiteContainer0.<>p__Site1 == null)
    {
        <Main>o__SiteContainer0.<>p__Site1 =
          CallSite<Action<CallSite, Type, object>>.Create(
          Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "WriteLine", null,
            typeof(Program), new CSharpArgumentInfo[] {
           CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType |            
               CSharpArgumentInfoFlags.UseCompileTimeType, null),        
           CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
     }
    <Main>o__SiteContainer0.<>p__Site1.Target(
         <Main>o__SiteContainer0.<>p__Site1, typeof(Console), p);
    Console.WriteLine(s);
}
藍底字是var的效果,很明顯的,編譯器於編譯時期就已經依據右賦值的原則,將s決議成int型別,因此在執行上,與你明確將其定義為int型別是一樣的。
但紅底字的是dynamic的效果,這一連串的程式碼是由編譯器所產生並編譯的,如同LINQusing的模式一樣,C#編譯器是少見的會透過兩段式編譯的編譯器,第一段是將簡化字展開,例如usingLINQdynamicvar,然後再進行第二次編譯。回到主軸,dynamic的效果便是如此,展開成一連串的物件宣告及函式呼叫,簡而言之,當C#遇到dynamic時,會將其展開成為一連串類似Reflection呼叫的程式碼,這跟你使用以下的程式碼是差不多的結果。

 

class Program
{
        static void Main(string[] args)
        {         
            Type t = Type.GetType("ConsoleApplication1.Person");
            object o = Activator.CreateInstance(t, false);
            t.InvokeMember("Hello", System.Reflection.BindingFlags.Public |
                               System.Reflection.BindingFlags.Instance |
                               System.Reflection.BindingFlags.InvokeMethod, null, o, null);
        }
    }
 
    public class Person
    {
        public string ID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
當然,你我都知道Person沒有Hello這個方法,這只是要模擬dynamic的行為模式,事實上,dynamic背後隱藏的技術遠比此複雜多了,也不是單純的Reflection,它包含了利用Lambda ExpressionDLR來產生並動態編譯呼叫的程式碼,還有快取已編譯結果等等,其執行效率高過Reflection,後面我會對此做進一步深入的介紹。
 
var必須於宣告時即給與右賦值的模式不同,dynamic允許你於任何時候賦值,也不像var般僅能用於變數宣告,dynamic可以用在傳入參數(呼叫函式時)、傳出值(函式傳回值),例如下面這個用法。

 

static void Main(string[] args)
{
     Console.WriteLine(Sum(15, 15));
     Console.WriteLine(Sum(15.5, 15.5));
     Console.WriteLine(Sum("code6421 ", " dotblogs"));
     Console.WriteLine(Sum(DateTime.Now, TimeSpan.FromDays(5)));
     int number = Sum(25, 25);
     Console.WriteLine(number);
     Console.ReadLine();
}
 
public static dynamic Sum(dynamic p1, dynamic p2)
{
      return p1 + p2;
}
下列是執行結果:

 

30
31
code6421 dotblogs
2010/1/27 下午 11:07:09
50
你是否由此例嗅到了一種特殊的寫法?沒有的話,那麼請注意再看一次,但請記住一點,這麼寫的缺點是,你永遠不知道使用者會如何用這個函式,所以加註說明及錯誤時處理是很重要的,這是dynamic的進階用法,打開的是天堂的大門還是內藏邪惡的寶盒,端看用的人而定。最後值得一提的是,dynamic是隱式的object,所以在處理函式overload時,要特別注意這點,LOLOTA的文章裡有對此介紹。
 
dynamic 應用:COM
 
     dynamicCOM Interop時也提供了相當方便的機制,原本於C# 3.0中,我們要這樣寫才能在Excel中添加一個值。

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using excel = Microsoft.Office.Interop.Excel;
 
namespace ConsoleApplication11
{
    class Program
    {
        static void Main(string[] args)
        {
            excel.ApplicationClass app =
                    new excel.ApplicationClass();
            app.Visible = true;
            excel.Workbook owb = app.Workbooks.Add(Missing.Value);
            excel.Worksheet ows =
              (excel.Worksheet)owb.ActiveSheet;
            ows.Cells[1, 1] = 15;
            Console.ReadLine();
            app.Quit();
        }
    }
}
請注意紅字部份,其實我們早就知道ActiveSheetexcel.Worksheet型別,但由於COM Interop的設計,ActiveSheet的屬性是object,必須通過轉型才能使用其Cells屬性,這是很煩的寫法,C# 4.0中,我們可以這麼寫:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using excel = Microsoft.Office.Interop.Excel;
 
namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            excel.Application app = new excel.Application();
            app.Visible = true;
            excel.Workbook owb = app.Workbooks.Add();
            excel.Worksheet ows = owb.ActiveSheet;
 
            ows.Cells[1, 1] = 15;
            Console.ReadLine();
            app.Quit();
        }
    }
}
由上而下,第一段紅字部份,我們已經不是使用ApplicationClass了,而是Application,或許你會覺得很奇怪,明明Application是個Interface,怎麼能夠用來new?這歸功於下圖的設定,而且這個設定是預設為開的。
1
 
 
 
當你將一個Interop Assembly設定為Embed Interop TypesTrue時,C# 4.0編譯器會為你提取使用到的Interface的部份出來,而省略掉未用到及Wrapper的實作部份,因此當Embed Interop Typestrue時,你照以往的寫法是無法通過編譯的:

 

Using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using excel = Microsoft.Office.Interop.Excel;
 
namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            excel.ApplicationClass app = new excel.ApplicationClass();
            app.Visible = true;
            excel.Workbook owb = app.Workbooks.Add(Missing.Value);
            excel.Worksheet ows = (excel.Worksheet)owb.ActiveSheet;
 
            ows.Cells[1, 1] = 15;
            Console.ReadLine();
            app.Quit();
        }
    }
}
必須將Embed interop Types設為False,上面這段程式碼才能通過編譯。
那麼Embed Interop Types的實際行為究竟為何?開了後,為何第二段紅字與第三段紅字,看起來會如此精簡?

 

excel.Application app = new excel.Application();
app.Visible = true;
excel.Workbook owb = app.Workbooks.Add();
excel.Worksheet ows = owb.ActiveSheet;
從原始Office Interop Assembly開始說起好了,當初Microsoft為了讓設計師能方便的於.NET中操控Office,提供了PIA(Primary Interop Assemblies),但因為語言及平台的限制,無法將其完整型別於C#中呈現出來,所以我們在C# 3.0時,得用硬轉的方式將函式的回傳值轉成真正的型別來操作。

 

excel.Worksheet ows = (excel.Worksheet)owb.ActiveSheet;
但在C# 4.0中,當Embed Interop Types設為True時,當下達以下的程式碼時,C#編譯器便進行一連串的轉換,包含提取COM Interop Assembly中的定義,然後將其嵌入現在的Assembly中,再接著將以下程式碼進行展開

 

excel.Application app = new excel.Application();
成為下面這樣:

 

Application app = (Application) Activator.CreateInstance(Type.GetTypeFromCLSID(
new Guid("00024500-0000-0000-C000-000000000046")));
這是為何我們可以於C# 中以new建立Application這個interface的原因,事實上,C# 4.0會將其轉換為CreateInstace的呼叫,透過CLSID來建立ExcelApplication物件。
當你觀察當Embed Interop Typesfalse時於專案中對於Interop Reference,會看到下面這張圖(Microsoft.Office.Interop.ExcelVisual Studio 2010目錄下Visual Studio Tools for Office\PIA)
2
 
但當embed interop typestrue時,展開的卻又是別一番景象:
3
噹噹噹噹!C# 4.0硬是把PIA的定義搬到現在的Assembly來了,簡單的說,C# 4.0Embed Interop types時,所編譯出來的執行檔,是不需要PIA的。
OK,我們解開了第一段Application的介面是如何變成物件,也解開了Embed interop types的真正面目,現在是時候看第三段紅字部份了:

 

excel.Worksheet ows = owb.ActiveSheet;
為何在C# 3.0要轉型,在C# 4.0就不用?答案跟Embed interop types有很大關連,因為embed interop types的機制,使得C# 4.0編譯器介入產生程式所用到的COM Interop TypesC# 4.0很聰明的把原本object的型別以dynamic取代,所以!此時owb.ActiveSheetdynamic型別,當然能夠指給excel.Worksheet型別了,事實上,你喜歡的話這樣寫也行:

 

int ows = owb.ActiveSheet;
只是我們都知道,owb.ActiveSheet的真正型別是excel.Worksheet,所以這在執行時期會丟出例外,還記得dynamic的真言嗎?
【我知道我在做什麼,你就別管了!】。至於第二段紅字:

 

excel.Workbook owb = app.Workbooks.Add();
則是C# 4.0的另一新功能,Optional Parameters,簡單的說,這個由C# 4.0幫我們產生的定義如下:

 

public excel.Workbook Add(object template = Type.Missing)
但別太興奮了,如果由我們自己來寫這段程式碼,是完全無法通過編譯的,因為Optional Parameters的賦值需要常數,而Type.Missing不是。
那為何C# 4.0可以產生這種定義?
4
 
唔!編譯器總有些特權的。好了,耍了一大圈,總歸一句話,C# 4.0dynamicembed interop types造就了NO PIAOffice操控,也簡化了程式碼的撰寫。
dynamicCOM的應用還不只於此,你也可以直接這樣寫:

 

Type t = Type.GetTypeFromProgID("Word.Application");
dynamic _wordApp = Activator.CreateInstance(t, false);
_wordApp.Visible = true;
embed interop types都不用,就像在ScriptDelphiVB般的寫法,這是Variant的用法,也就是當年為人所垢病的Un-Type Programming,但它很方便是無庸至疑的。
 
 
dynamic 應用:SilverilghtJavaScript間的互動
 
 
 Visual Studio 2010 CTP時期,曾經出現過下列的Silverlight程式範例:

 

xxx.Xaml.cs
dynamic win = HtmlPage.Window.AsDynamic();
win.callSomeJavaScript("Hello");
xxx.aspx
.......
<script language="javascript">
   function callSomeJavaScript(msg) {
   //do something
   }
</script>
意思是,透過dynamic的協助,我們可以脫離原本Silverlight 2/3的寫法。

 

HtmlPage.Window.Invoke("callSomeJavaScript","Hello");
不過這個例子在Beta 2時消失了,原因不明,但基本上我們可以用dynamic的延展機制把它拿回來。

 

.aspx
........
<body>
<script language="javascript" type="text/javascript">
    function alertMsg(msg) {
        alert(msg);
    }
</script>
...............
.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Dynamic;
using Microsoft.CSharp;
using System.Windows.Browser;
 
namespace SilverlightApplication3
{
    public partial class MainPage : UserControl
    {
        private dynamic _win = new HtmlWindowObject(HtmlPage.Window);
 
        public MainPage()
        {
            InitializeComponent();           
        }
 
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            _win.alertMsg("Hello Dynamic");
        }
    }
 
    public class HtmlWindowObject : DynamicObject
    {
        private HtmlWindow _win = null;
 
        public HtmlWindowObject(HtmlWindow win)
            : base()
        {
            _win = win;
        }
 
        public override bool TryInvokeMember(InvokeMemberBinder binder,
                        object[] args, out object result)
        {
            try
            {
                result = _win.Invoke(binder.Name, args);
                return true;
            }
            catch (Exception ex)
            {
                result = null;
            }
            return false;
        }
    }
}
OK,我知道我跳tone了,等會再回來解釋DynamicObject

 

註:此例是以Silverlight 4為例,DynamicObjectSilverlight 3已經沒有支援了,即使在Silverlight 4,仍然要手動加入對Microsoft.CSharp.dll的參考,此dll位於Silverlight 4 SDK\Libraries\Client目錄下。
 
 
 
dynamic 應用:IronPhyton
 
     IronPhytonIronRuby也由dynamic型別得到了協助,LOLOTA有針對此寫了一篇文章,我就不贅述了。
 
 
 
dynamic 應用:DynamicObject
 
     除了提供dynamic型別外,C# 4.0也提供了延展用的DynamicObject物件,透過這個物件,我們可以自訂當dynamic型別變數被賦與此DynamicObject之繼承者於呼叫函式、屬性存取時的行為模式,下面是一個應用DynamicObject的例子:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Dynamic;
 
namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic obj = new PropertyCollectionObject();
            obj.Name = "code6421";
            obj.Age = 15;
            obj.City = "Taipei";
            Console.WriteLine(obj.Name);
            Console.WriteLine(obj.Age);
            Console.WriteLine(obj.City);
            obj.ToXml();
            Console.ReadLine();
 
        }
    }
 
    public class PropertyCollectionObject : DynamicObject
    {
        private Dictionary<dynamic, dynamic> _bags =
                 new Dictionary<dynamic, dynamic>();
 
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (_bags.Keys.Contains(binder.Name))
                result = _bags[binder.Name];
            else
                result = null;
            return result != null;
        }
 
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (_bags.Keys.Contains(binder.Name))
                _bags[binder.Name] = value;
            else
                _bags.Add(binder.Name, value);
            return true;
        }
 
        public override bool TryInvokeMember(InvokeMemberBinder binder,
                     object[] args, out object result)
        {
            result = null;
            if (binder.Name == "ToXml")
            {
                var xml = new XElement("Root",
                     from s1 in _bags select new XElement(s1.Key, s1.Value));
                Console.WriteLine(xml);
                return true;
            }           
            return false;
        }
    }
}
本例的執行結果如下:

 

code6421
15
Taipei
<Root>
 <Name>code6421</Name>
 <Age>15</Age>
 <City>Taipei</City>
</Root>
當我們將PropertyCollectionObject指給一個dynamic型別變數時,C#編譯器會查詢PropertyCollectionObject是否是實作IDynamicMetaObjectProvider,如果是的話就會透過其來取得Metadata,接下來展開的動作就會依據metadata來進行,不過我們不需要太深入IDynamicMetaObjectProvider,因為DynamicObject就是一個實作了IDynamicMetaObjectProvider的物件,透過它的協助,我們可以跳過metadata的定義,直接以覆載其方法來協助dynamic 型別的動態呼叫。
 
以屬性設定的例子來說,當obj.Name="code6421"被編譯器展開時,會被解譯成對DynamicObject.TrySetMember函式的呼叫,於此我們以Dictionary<dynamic,dynamic>來儲存屬性值的設定,也就是說當obj.Name這行跑完時,_bags會有一組<Name,"code6421">的元素,同樣的之後的AgeCity都是一樣的。
 
當展開Console.WriteLine(obj.Name)時,會變成對TryGetMember的呼叫,於此我們由_bags取出對應的值。
 
當展開ToXml函式呼叫時,會形成對TryInvokeMember的呼叫,於此我們由_bags取值,然後轉成XML後印出。
 
有了這些知識,再回頭看看SilverlightJavaScript整合,就能對DynamicObject有更深的認識了。
 
 
 
dynamic的真實面目
 
 
     現在是時候來解開dynamic背後的秘密了, dynamic型別其實會被C#編譯器展開為Expression Tree,然後進行編譯後產生一個delegate來呼叫,是的!dynamic不是單純的透過ReflectionInvokeMember來做的,而是藉助於Reflection的型別資訊來動態產生Expression Tree後編譯成delegate的,可以將其想像為,C#編譯器將dynamic展開為一段合理的呼叫函式程式碼,然後進行編譯,最後取出delegate
     歸功於C# 3.0Lambda機制,原本以前要做動態編譯時,得先產生程式碼,再透過C# Complier來編譯成DLL才能呼叫,在Lambda機制加入後,我們可以直接在不產生任何DLL的情況下,
將動態型別"注入"現在執行中的程式,C# 4.0更充份的應用這點完成dynamic型別的設計,下面是一個模擬例子。

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
 
namespace ConsoleApplication7
{
    class Program
    {
        static void Main(string[] args)
        {
            object o = new TestObject();
            Action a = MakeCallExpression(o, "Hello");
            a();
            Console.ReadLine();
        }
 
        static Action MakeCallExpression(object o, string method)
        {
            Delegate invokeDelegate = Expression.Lambda(
                           Expression.Call(Expression.Constant(o),
                           o.GetType().GetMethod(method))).Compile();
            Action realDel = (Action)invokeDelegate;
            return realDel;
        }
    }
 
    public class TestObject
    {
        public void Hello()
        {
            Console.WriteLine("Hello Expression");
        }
    }
}
程式中,我們並未直接透過TestObject來呼叫Hello函式,而是透Lambda機制,動態的產生呼叫Hello函式的delegate,然後對Hello進行呼叫。
OK,一般來說,其實讀者們只需要知道一件事就夠了,這種手法相較於Reflection來說是較有效率的,因為呼叫Hellodelegate是編譯過後的碼,執行起來比每次都要走過一段Reflection過程的InvokeMember會來得有效率,雖然在首次產生delegate時必須耗一點時間,但代價是更快的執行效率,而且透過快取產生過的delegeate,可以減輕編譯時所帶來的效率影響。
真實的dynamic處理行為比此例複雜多了,除了加入編譯後的delegate快取機制外(所以在第二次呼叫同名函式時,會比第一次快),也處理了泛型、覆載等課題。但這些玩弄Expression的技巧,還是留給Framework的設計師吧。