看著一個程式語言的誕生,然後逐步追隨其成長,是一件相當有趣的事,特別是該程式語言一直都處於主流語言的時候,很可惜的,這種機會並不常有,C#是在我程式生涯中,唯一一個從其出生即跟隨至今的程式語言。在C#誕生之初,也是Anders Hejlsberg離開Borland之後的數年後
C# 4.0 New Feature : Dynamic Programming
文/黃忠成
C# 的10 年
看著一個程式語言的誕生,然後逐步追隨其成長,是一件相當有趣的事,特別是該程式語言一直都處於主流語言的時候,很可惜的,這種機會並不常有,C#是在我程式生涯中,唯一一個從其出生即跟隨至今的程式語言。
在C#誕生之初,也是Anders Hejlsberg離開Borland之後的數年後,對於一個老Delphi設計師而言,對於C#的感情因素遠比其語言本質來得重,Anders是Delphi的主要掌舵者,他成功的將Delphi帶入一個RAD工具前所未進過的殿堂,在他離開Borland而建立C#後,我們在C#上看到了Delphi的影子,PME的設計,Refection的進化,諸多設計都可見到Delphi的背影。當然,我並不是說C#是抄襲Delphi,畢竟當時也有人說C#與Java很像,C#與C++很像,這些其實都不重要,重要的是,C#是擷取了許多語言的優點而成的,對於程式設計師而言,這點就足夠了。
C# 1.0隨著.NET Framework一起問市,創始者Anders將C# 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.0的dynamic型別卻可以讓這段程式通過編譯,將一變數宣告為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的效果,這一連串的程式碼是由編譯器所產生並編譯的,如同LINQ與using的模式一樣,C#編譯器是少見的會透過兩段式編譯的編譯器,第一段是將簡化字展開,例如using、LINQ、dynamic、var,然後再進行第二次編譯。回到主軸,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 Expression及DLR來產生並動態編譯呼叫的程式碼,還有快取已編譯結果等等,其執行效率高過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
dynamic於COM 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(); } } } |
請注意紅字部份,其實我們早就知道ActiveSheet是excel.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 Types為True時,C# 4.0編譯器會為你提取使用到的Interface的部份出來,而省略掉未用到及Wrapper的實作部份,因此當Embed Interop Types為true時,你照以往的寫法是無法通過編譯的:
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來建立Excel的Application物件。
當你觀察當Embed Interop Types為false時於專案中對於Interop 的 Reference,會看到下面這張圖(Microsoft.Office.Interop.Excel在Visual Studio 2010目錄下的Visual Studio Tools for Office\PIA下):
圖2
但當embed interop types為true時,展開的卻又是別一番景象:
圖3
噹噹噹噹!C# 4.0硬是把PIA的定義搬到現在的Assembly來了,簡單的說,C# 4.0在Embed 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 Types,C# 4.0很聰明的把原本object的型別以dynamic取代,所以!此時owb.ActiveSheet是dynamic型別,當然能夠指給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.0的dynamic及embed interop types造就了NO PIA的Office操控,也簡化了程式碼的撰寫。
但dynamic於COM的應用還不只於此,你也可以直接這樣寫:
Type t = Type.GetTypeFromProgID("Word.Application"); dynamic _wordApp = Activator.CreateInstance(t, false); _wordApp.Visible = true; |
連embed interop types都不用,就像在Script、Delphi、VB般的寫法,這是Variant的用法,也就是當年為人所垢病的Un-Type Programming,但它很方便是無庸至疑的。
dynamic 應用:Silverilght與JavaScript間的互動
在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為例,DynamicObject在Silverlight 3已經沒有支援了,即使在Silverlight 4,仍然要手動加入對Microsoft.CSharp.dll的參考,此dll位於Silverlight 4 SDK\Libraries\Client目錄下。 |
dynamic 應用:IronPhyton
IronPhyton與IronRuby也由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">的元素,同樣的之後的Age、City都是一樣的。
當展開Console.WriteLine(obj.Name)時,會變成對TryGetMember的呼叫,於此我們由_bags取出對應的值。
當展開ToXml函式呼叫時,會形成對TryInvokeMember的呼叫,於此我們由_bags取值,然後轉成XML後印出。
有了這些知識,再回頭看看Silverlight與JavaScript整合,就能對DynamicObject有更深的認識了。
dynamic的真實面目
現在是時候來解開dynamic背後的秘密了, dynamic型別其實會被C#編譯器展開為Expression Tree,然後進行編譯後產生一個delegate來呼叫,是的!dynamic不是單純的透過Reflection的InvokeMember來做的,而是藉助於Reflection的型別資訊來動態產生Expression Tree後編譯成delegate的,可以將其想像為,C#編譯器將dynamic展開為一段合理的呼叫函式程式碼,然後進行編譯,最後取出delegate。
歸功於C# 3.0的Lambda機制,原本以前要做動態編譯時,得先產生程式碼,再透過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來說是較有效率的,因為呼叫Hello的delegate是編譯過後的碼,執行起來比每次都要走過一段Reflection過程的InvokeMember會來得有效率,雖然在首次產生delegate時必須耗一點時間,但代價是更快的執行效率,而且透過快取產生過的delegeate,可以減輕編譯時所帶來的效率影響。
真實的dynamic處理行為比此例複雜多了,除了加入編譯後的delegate快取機制外(所以在第二次呼叫同名函式時,會比第一次快),也處理了泛型、覆載等課題。但這些玩弄Expression的技巧,還是留給Framework的設計師吧。