黃忠成

風雪之閣- i live,so i writing
文章數 - 133, 回應數 - 126, 引用數 - 0


關於我:



黃忠成

  • 資深.NET 技術顧問
  • Run! PC 雜誌專欄作者
  • 程序員雜誌文章作者
  • PC Magazine 雜誌專欄作者
  • MSDN 專欄作者
  • MSDN 特約專屬講師
  • Microsoft .NET專屬講師
  • 台灣微軟特約技術顧問
  • 台灣微軟最有價值專家


  • 批評,指教,鼓勵, 請 寫信給我
    轉載文章請使用連結模式,
    請勿整篇Copy! 謝謝!


    我所提供的教育訓練:

    Windows Forms
    ASP.NET 2.0
    如有課程需要,請與我聯絡!

  • 我的著作:

  • 文章標籤

    全部標籤

    每月文章

    文章分類

    C# 4.0 New Feature : Dynamic Programming

     

    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的設計師吧。

     


    DotBlogs Tags: .NETFramework

    posted on 2010/1/23 14:14 | 1 人收藏 6 人推薦 我要推薦 | 閱讀數 : 1405 | 文章分類 [ .NET Framework Visual Studio ] 訂閱

    Feedback

    # re: C# 4.0 New Feature : Dynamic Programming 回覆

    請問使用 dynamic 可以讓我不必參考某一函式庫/COM 元件嗎? 我是想說不用參考的話,程式比較不會綁死在所參考之特定版本函式庫/COM 元件...只要多加一些例外處理即可...
    2010/1/25 下午 04:02 | ChrisTorng

    # re: C# 4.0 New Feature : Dynamic Programming 回覆

    yes.

    2010/1/25 下午 04:58 | code6421

    # re: C# 4.0 New Feature : Dynamic Programming 回覆

    所以這也是 dynamic 的一個很有意義的應用吧...
    2010/1/26 上午 08:18 | ChrisTorng

    回應

    標題
    姓名
    電子郵件 (將不會被顯示)
    個人網頁
    內容 
      登入後使用進階評論  
    Please add 8 and 1 and type the answer here:

    Powered by: