溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

.NET 4.0新特性Dynamic是什么

發(fā)布時間:2021-12-27 09:48:50 來源:億速云 閱讀:198 作者:小新 欄目:編程語言

這篇文章將為大家詳細(xì)講解有關(guān).NET 4.0新特性Dynamic是什么,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

前段時間看過一些關(guān)于dynamic這個C#4中的新特性,看到有些朋友認(rèn)為dynamic的弊大于利,如無法使用編譯器智能提示,無法在編譯時做靜態(tài)類型檢查,性能差等等。因此在這篇文章中我將就這些問題來對dynamic做一個較詳細(xì)的介紹,希望通過這篇文章,能使大家對dynamic關(guān)鍵字有個更深入的認(rèn)識。

dynamic介紹

相信很多人應(yīng)該都已經(jīng)對Anders Hejlsberg在PDC2008上所做的那篇”The Future of C#”(注1) 都有所了解了,當(dāng)時的這篇演講已經(jīng)介紹了C#4.0的一些最重要的特性。Anders提到C#的未來時候指出C#4.0的特點是動態(tài)編程,他同時也列舉了很多在4.0中關(guān)于動態(tài)編程的例子,這里我具體講一講他首先提到的dynamic關(guān)鍵字。

提到dynamic,我首先想到的是var關(guān)鍵字。事實上,當(dāng)var在C#3.0中剛剛出現(xiàn)的時候就引起了一些人的質(zhì)疑,后來微軟解釋var只是隱含類型聲明符,并且只能用作局部變量,它其實仍然是強(qiáng)類型,只不過是編譯器由初始化結(jié)果推斷而來,所以對這個變量仍然可以可以使用VS的只能提示?,F(xiàn)在dynamic則真正往動態(tài)特性邁進(jìn)了一大步,根據(jù)Anders的解釋,dynamic是指動態(tài)的靜態(tài)類型,也就是說它本質(zhì)上仍然是靜態(tài)類型,只不過它告訴編譯器忽略對它的靜態(tài)類型檢查,它會在運行時才進(jìn)行類型檢查,它可以應(yīng)用在基本上所有的C#類型上面,如方法,操作符,索引器,屬性,字段,它其實是通過統(tǒng)一的方式來調(diào)用方法、屬性等操作。

dynamic主要用與需要與外界(COM,DLR,HTML DOM,XML等)的交互的場合,在這些時候,你很可能不能確定這些對象的具體類型而僅僅知道它的一些屬性,如方法等,因此這些時候你僅僅告訴編譯器你需要在程序運行這里執(zhí)行這些方法,至于操作對象是什么,你可能并不關(guān)心。這個時候,靜態(tài)類型無法幫你解決問題,因為它們是在編譯時就已經(jīng)決定了的,反射雖然能做大,但畢竟太麻煩,而且效率較低。因此dynamic適時的出現(xiàn)了,它用編譯時類型檢查缺失的代價來實現(xiàn)讓程序員看起來很干凈的代碼。

dynamic的聲明和使用很簡單,跟javascript中的var基本是一致的。需要注意的是,在編譯代碼之前我們首先需要安裝VS 2010 Beta2或Visual C# Express 2010,我這里安裝的是C# Express(注2)。e.g.代碼1:

class Program{static void main()  {  dynamic a=7;a.Error=”Error”;a=”Test”;a.Run();  }}

這段代碼可以通過編譯,但無法運行。C#編譯器允許你對a對象調(diào)用任何方法或其他成員,它并不會在編譯時檢查這些成員調(diào)用是否合法,取而代之的是,編譯器會在運行時檢查實際的對象是否具有相應(yīng)的方法,如果有,則調(diào)用,否則,CLR會拋出異常。如,下面的代碼將可以正常執(zhí)行:

static dynamic Sum(dynamic obj1,dynamic obj2){    return obj1.Age+obj2.Age;  }  static void main(){  var animal=new{Sex=”Male”,Age=”5”};  var plant=new{Class=”草本”,Age=100};  dynamic ageCount=Sum(animal+plant);}

這里我們對兩個不同對象的年齡相加,在sum函數(shù)中,我們根本就不關(guān)心我們調(diào)用的對象是什么,而僅僅需要知道他們都有Age成員,并且這個成員能夠進(jìn)行+操作符運算。事實上,在與DLR的交互和Silverlight中,這種場景將會大量存在,因此dynamic在這些場合將會非常有用。

探討玩使用情況之后我們再來看看dynamic到底是如何實現(xiàn)的。實際上通過Reflector查看代碼你會發(fā)現(xiàn)它顯示的代碼是這樣的:


 

internal class Program{    // Methods    private static void Main(string[] args)      {        object a = 7;          if (<Main>o__SiteContainer0.<>p__Site1 == null)        {              <Main>o__SiteContainer0.<>p__Site1 =   CallSite<Func<CallSite, object, string, object>>.Create  (Binder.SetMember(CSharpBinderFlags.None, "Error", typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None,   null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.LiteralConstant | CSharpArgumentInfoFlags.UseCompileTimeType,   null) }));          }        <Main>o__SiteContainer0.<>p__Site1.Target  (<Main>o__SiteContainer0.<>p__Site1, a, "Error");          a = "Test";          if (<Main>o__SiteContainer0.<>p__Site2 == null)          {              <Main>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, object>>.Create(Binder.InvokeMember  (CSharpBinderFlags.ResultDiscarded, "Run", null, typeof(Program), new CSharpArgumentInfo[]   { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));          }        <Main>o__SiteContainer0.<>p__Site2.Target(<Main>o__SiteContainer0.<>p__Site2, a);      }    // Nested Types    [CompilerGenerated]    private static class <Main>o__SiteContainer0      {        // Fields        public static CallSite<Func<CallSite, object, string, object>> <>p__Site1;          public static CallSite<Action<CallSite, object>> <>p__Site2;      }}

大家可以可以從代碼中看到,實際上dynamic對象就是object對象,在編譯的時候編譯器會給每個不同的方法在類的嵌套靜態(tài)類SiteContainer生成不同的CallSite字段,這些CallSite會將綁定調(diào)用方法的信息,當(dāng)需要真正調(diào)用方法的時候,它會調(diào)用由編譯器生成的嵌套靜態(tài)類SiteContainer中的CallSite來調(diào)用實際方法,這里CLR通過將調(diào)用的方法設(shè)置為靜態(tài)變量來達(dá)到Cache的目的,也就是如果該方法是第一次調(diào)用,那么它會創(chuàng)建該類型,否則,它會直接調(diào)用之前生成的靜態(tài)CallSite類型來調(diào)用實際方法。這對大批量重復(fù)操作來說,可以顯著提高效率。我將在后文對此進(jìn)行詳細(xì)測試。

使用舉例

好了,介紹完dynamic之后我們來討論下這個新特性的使用場景吧,關(guān)于dynamic的使用例子,其實Anders 在他的演講中已經(jīng)展示了很多例子。我這里首先對這些例子做個總結(jié),1. SilverLight中與javascript交互,在視頻中他不僅演示了我們?nèi)绾握{(diào)用HTML中的Javascript 方法,Anders甚至給我們演示了如何直接在C#代碼中加入Javascipt方法;2. 這個例子是C#和動態(tài)語言IronPython交互的情況,在這個例子中,他演示我們?nèi)绾沃苯诱{(diào)用一個在Python中定義的Calculate方法。3. 除了這些,他還演示了通過Dynamic干凈直觀的操作XML。

這里我額外補(bǔ)充兩個使用dynamic的例子,實際上這只是我提供的一種思路,如果你覺得他們的實現(xiàn)并不好,我很歡迎你提出不同的意見或更恰當(dāng)?shù)睦印?/p>

1. 讓泛型支持操作符重載。

我曾經(jīng)在復(fù)習(xí)泛型的時候提到過.NET泛型是不支持操作符的,因為操作符是編譯器決定的,而泛型是運行時決定。所以如果你想對兩個泛型變量進(jìn)行+的操作是無法通過編譯的。事實上,在Linq實現(xiàn)Sum操作的時候也是通過對所有基本數(shù)據(jù)類型(e.g.int,long)的重載來實現(xiàn)的。但有了dynamic,這種操作將變得可能。e.g.


class MyList<T>{      public List<T> Items { get; set; }      public T Sum()    {          dynamic result = default(T);          foreach (var temp in Items)          {              result += temp;        }          return result;    }}

這里由于我們將Result聲明為dynamic類型,所以編譯器不會檢查其是否能進(jìn)行+操作,但這里我們有個契約就是這個求和函數(shù)中的類型應(yīng)支持+運算?,F(xiàn)在我們可以對這個類進(jìn)行如下操作:

MyList<int> l1=new MyList<int>()&hellip;dynamic  a= l1.Sum();  MyList<String> l2=new MyList<String>()&hellip;. a=l2.Sum();

另外一個就是XML操作了,由于XML中所有的屬性都是string類型的,但有時我們又卻是需要使用其實際類型,這時dynamic也很有用。這里給出我看到的一個認(rèn)為不錯的例子,你可以參看這篇文章:

首先我們定義一個繼承DynamicObject的動態(tài)類型。

代碼 public class DynamicXMLNode : DynamicObject{      XElement node;      public DynamicXMLNode(XElement node)    {        this.node = node;      }      public DynamicXMLNode()      {    }     public DynamicXMLNode(String name)      {        node = new XElement(name);      }     public override bool TrySetMember(        SetMemberBinder binder, object value)      {          XElement setNode = node.Element(binder.Name);          if (setNode != null)              setNode.SetValue(value);          else        {              if (value.GetType() == typeof(DynamicXMLNode))                  node.Add(new XElement(binder.Name));              else                  node.Add(new XElement(binder.Name, value));          }          return true;      }      public override bool TryGetMember(GetMemberBinder binder, out object result)      {          XElement getNode = node.Element(binder.Name);          if (getNode != null)          {              result = new DynamicXMLNode(getNode);              return true;        }          else        {              result = null;              return false;          }    }} 定義好動態(tài)XML節(jié)點類之后,我們可以像下面這樣使用它。 dynamic contact = new DynamicXMLNode  ("Contacts");contact.Name = "Patrick   Hines";contact.Phone = "206-555-0144";  contact.Address = new DynamicXMLNode();  contact.Address.Street = "123 Main St";  contact.Address.City = "Mercer Island";  contact.Address.State = "WA";contact.Address.Postal = "68402";

是不是真正做到了XML對象和C#對象的無縫銜接了?

不足

1. 無法支持?jǐn)U展方法。由于擴(kuò)展方法能否被加載是根據(jù)上下文,如DLL的引用和命名空間的引用這些靜態(tài)信息來獲取的,目前dynamic還不支持調(diào)用擴(kuò)展方法。這也意味著Linq沒辦法被dynamic支持。

2.無法支持匿名方法。匿名方法(Lamda表達(dá)式)無法作為一個動態(tài)方法調(diào)用的參數(shù)傳遞。編譯器沒辦法獲取一個匿名方法的具體類型,所以它也就沒辦法綁定匿名方法了。

性能?

很多朋友考慮dynamic一個很重要的缺點就是認(rèn)為它本質(zhì)還是object類型,只不過CLR在運行時候通過反射來達(dá)到動態(tài)調(diào)用的目的。確實沒錯,跟普通方法調(diào)用比較,動態(tài)類型的方法在第一次調(diào)用的時候要做很多的事情,它需要把調(diào)用的信息存放起來,然后在真正用到這個方法的時候通過CallSite.Create()來調(diào)用實際的方法,當(dāng)然這個Create里面也是通過反射來達(dá)到目的的。

不過這是否意味著dynamic還不如反射的性能呢?答案是否的,事實上,看我上面的代碼你會發(fā)現(xiàn),動態(tài)對象在每次調(diào)用方法的時候都會先判斷這個callSite對象是否是空的,如果不是空的,它可以直接調(diào)用而不需要重新實例化,所以如果你的對象的方法需要有很多重復(fù)使用的時候,它的性能其實并不會太差。下面我將給出測試的代碼。

這里我的測試目標(biāo)是對一個大型數(shù)組進(jìn)行求和操作,在這個測試中,由于系統(tǒng)是XP,我使用了裝配腦袋寫的性能計數(shù)器,你可以參看對老趙寫的簡單性能計數(shù)器的修改。首先,我需要定義一個支持+操作符的結(jié)構(gòu)(我本來想直接使用int,但測試的時候不知為何int的相加運算符無法調(diào)用)

public struct MyData{      public int Value;      public MyData(int value)      {          this.Value = value;      }      public static MyData operator +(MyData var1,MyData var2)      {          return new MyData(var1.Value+var2.Value);      }}

然后我為了免去重復(fù)初始化列表的過程,我簡單將普通方法調(diào)用,Dynamic方式調(diào)用和反射調(diào)用設(shè)計成一個嵌套類,見代碼:

public class MyTest{      public static List<MyData> Items   { get; set;   }      public MyTest(int count)      {        Items = new List<MyData>(count);          for (int i = 0; i < count; i++)        {            Items.Add(new MyData(1));          }    }      public void Run()      {          Console.WriteLine("Compare Times:  {0}",Items.Count);          CodeTimer.Time("Common", 1, new TestCommon());          CodeTimer.Time("Dynamic", 1, new TestDynamic());          CodeTimer.Time("Reflect", 1, new TestReflect());      }      public class TestCommon : IAction      {        public MyData Result { get; set; }          public void Run()          {            Result = default(MyData);            foreach (var d in Items)              {                Result += d;              }        }    }      public class TestDynamic : IAction      {        public dynamic Result { get; set; }        public void Run()          {              Result = default(dynamic);              foreach (dynamic d in Items)              {                Result += d;              }        }      }      public class TestReflect : IAction      {        public MyData Result { get; set; }          public void Run()          {            Result = default(MyData);              Type type = typeof(MyData);   MethodInfo m = type.GetMethod("op_Addition", BindingFlags.Public | BindingFlags.Static);              foreach (var d in Items)            {                  Result = (MyData)(object)m.Invoke(null, new object[] { Result, d });              }        }    }}

最后是調(diào)用方法:

static void main(){      MyTest test = new MyTest(100000);      test.Run();      test = new MyTest(1000000);      test.Run();      test = new MyTest(10000000);      test.Run();}

最后我將給出測試結(jié)果,不過我發(fā)現(xiàn)每次測試結(jié)果數(shù)據(jù)好像都有所不同,但數(shù)據(jù)規(guī)律大致相似。

 

普通方法

動態(tài)調(diào)用

反射

數(shù)組大小

100,000

Time Elapsed

9ms

274ms

442ms

Time Elapsed (one)

9ms

274ms

442ms

CPU time

15,625,000ns

296,875,000ns

484,375,000ns

CPU Time (one)

15,625,000ns

296,875,000ns

484,375,000ns

Gen 0

0

1

5

Gen 1

0

0

0

Gen 2

0

0

0

數(shù)組大小

1,000,000

Time Elapsed

42ms

244ms

3,736ms

Time Elapsed (one)

42ms

244ms

3,736ms

CPU time

62,500,000ns

281,250,000ns

4,140,625,000ns

CPU Time (one)

62,500,000ns

281,250,000ns

4,140,625,000ns

Gen 0

0

7

20

Gen 1

0

0

0

Gen 2

0

0

0

數(shù)組大小

10,000,000

Time Elapsed

585ms

2,553ms

40,763ms

Time Elapsed (one)

585ms

2,553ms

40,763ms

CPU time

656,250,000ns

2,796,875,000ns

43,671,875,000ns

CPU Time (one)

656,250,000ns

2,796,875,000ns

43,671,875,000ns

Gen 0

0

30

205

Gen 1

0

1

4

Gen 2

0

0

0

從表格中我們大致可以看出,直接調(diào)用方法最快,并且產(chǎn)生的對象最少。通過反射方式不僅時間往往耗費較多,而且還會生產(chǎn)大量的對象。另外我們發(fā)現(xiàn)在1,000,000反而比100,000花費的時間要少,但生成的對象確實增多了。這一點我不太明白,同樣的對象通過Cache確實能提高效率,但我不知道為什么多做10倍的加法操作的動態(tài)方法調(diào)用反而會更快。另外,從圖中也可以看出基本上使用dynamic調(diào)用方法花費的時間是直接調(diào)用的5倍左右,在有些時候,這個性能損失所做的交換也是值得的。

上面的測試數(shù)據(jù)基本每次都會有所變化,但總體走勢是基本不變的,那就是花費時間common>dynamic>Reflect。因此我們可以認(rèn)為雖然dynamic確實會有性能損失,但有時候如果你的系統(tǒng)確實需要動態(tài)生成對象或動態(tài)調(diào)用方法的時候它還是可以考慮的,特別是如果你系統(tǒng)需要使用反射的而這種類型的操作又會有多次重復(fù)的情況下尤其值得考慮。

后記

關(guān)于dynamic關(guān)鍵字目前還沒有太多使用的用例,關(guān)于它到底好還是不好的爭論也一直沒有停止。事實上,一直到現(xiàn)在,都有很多人對dynamic持有懷疑和反對態(tài)度,他們認(rèn)為dynamic性能并不好,而且dynamic的到來使得編譯器的自動完成功能沒有了,同時還無法完成編譯時成員調(diào)用的檢查,這將使得普通程序員的出錯幾率大大增加。

不否認(rèn)dynamic使用不當(dāng)確實會導(dǎo)致程序員犯錯的幾率大大提高。然而,正如蜘蛛俠說的,能力越大,責(zé)任越大。其實微軟引入dynamic也是給了C#程序員比以往更強(qiáng)大的能力,但這強(qiáng)大的能力使用不當(dāng)也會造成錯誤。不過,我們能因此而說這個能力是丑惡的或者是壞的么?我想,能力其實沒有好壞之分,能力的好壞得看使用者,例如核能,同樣的道理,聰明的程序員可以把一個特性使用得優(yōu)雅高效,而愚蠢的程序員則恰好相反。其次,對這些動態(tài)語言可能存在的問題來說我們也可以通過其他方式盡量來避免。首先,類型或成員調(diào)用合法性的檢查其實我們可以通過單元測試得到最大保證。對一個健壯的大型程序來說,單元測試是必要的。其次,使用dynamic之后我們確實缺少了智能提示,但我并沒有提倡將dynamic用在所有的地方(事實上那樣也是錯了,因為它將造成程序的效率顯著降低),我的意思是你僅僅在你真正需要使用dynamic的時候才去使用它,這正是這個關(guān)鍵詞存在的理由。而這些需要使用dynamic的地方不會很多,我們也能明白在這些地方我們要用它來做什么。有了這兩點保證,我相信dynamic引起的不便也不會那么明顯。

最后,dynamic只是微軟給我們程序員的更多的一個選擇,如果你不喜歡它,你當(dāng)然可以在很多場合避免使用它,比如使用類型轉(zhuǎn)換,反射等等。用還是不用它,微軟把選擇權(quán)交給了我們程序員自己。另外,你也可以觀看來自C# Compiler Team更多關(guān)于Dynamic的介紹:C# 4.0 Dynamic with Chris Burrows and Sam Ng

附注:

注1: The future of C# ,Anders Hejlsberg, http://channel9.msdn.com/pdc2008/TL16/

注2: Visual C# Express 2010下載地址:http://www.microsoft.com/express/future/default.aspx

參考資料:

New features in C# 4.0

結(jié)果截圖:

.NET 4.0新特性Dynamic是什么 .NET 4.0新特性Dynamic是什么

.NET 4.0新特性Dynamic是什么

機(jī)器信息:

Thinkpad X200 7654;CPU:Intel Core2 Duo CPU p8400 2.25GHz;虛擬機(jī):VMWare 6.5;虛擬內(nèi)存:1G;虛擬機(jī)操作系統(tǒng):Windows XP;編譯器版本:Visual C# Express 2010 Beta2。

關(guān)于“.NET 4.0新特性Dynamic是什么”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI