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的設計師吧。
转载于:https://blog.51cto.com/h2appy/743662