您好,登錄后才能下訂單哦!
在很久之前,我寫了一片文章詳解C# 匿名對象(匿名類型)、var、動態類型 dynamic,可以借鑒。因為那時候是心中想當然的認為只有反射能夠在運行時解析對象的成員信息并調用成員方法。后來也是因為其他的事一直都沒有回過頭來把這一節知識給補上,正所謂亡羊補牢,讓我們現在來大致了解一下DLR吧。
DLR 全稱是 Dynamic Language Runtime(動態語言運行時)。這很容易讓我們想到同在C#中還有一個叫 CLR 的東西,它叫 Common Language Runtime。那這兩者有什么關系呢?這個后續再說
C#4動態功能是Dynamic Language Runtime(動態語言運行時,DLR)的一部分.DLR是添加到CLR的一系列服務,它允許添加動態語言,如Ruby和Python,并使C#具備和這些動態語言相同的某些功能.
DLR 是 C#4.0 新引進來的概念,其主要目的就是為了動態綁定與交互。
C#關鍵字 dynamic
DLR 首先定義了一個核心類型概念,即動態類型。即在運行時確定的類型,動態類型的成員信息、方法等都只在運行時進行綁定。與CLR的靜態類型相反,靜態類型都是在C#編譯期間通過一系列的規則匹配到最后的綁定。
將這種動態進行綁定的過程它有點類似反射,但其內部卻和反射有很大的不同。這個稍微會談到。
由動態類型構成的對象叫動態對象。
DLR一般有下列特點:
dynamic
。如dynamic x = GetReturnAnyCLRType()
DLR發展到現在,我們幾乎都使用了動態類型關鍵字 dynamic
以及還有引用DLR的類庫 Dapper等。
在我們不想創建新的靜態類做DTO映射時,我們第一時間會想到動態類型。也經常性的將dynamic作為參數使用。
這時候我們就要注意一些 dynamic 不為大多人知的一些細節了。
不是只要含有 dynamic 的表達式都是動態的。
什么意思呢,且看這段代碼dynamic x = "marson shine";
。這句代碼很簡單,就是將字符串賦值給動態類型 x。
大家不要以為這就是動態類型了哦,其實不是,如果單單只是這一句的話,C#編譯器在編譯期間是會把變量 x 轉變成靜態類型 object 的,等價于object x = "marson shine";
。可能有些人會驚訝,為什么C#編譯器最后會生成object類型的代碼。這就是接下來我們要注意的。
dynamic 于 object 的不可告人的關系
其實如果你是以 dynamic 類型為參數,那么實際上它就是等于 object 類型的。換句話說,dynamic在CLR級別就是object。其實這點不用記,我們從編譯器生成的C#代碼就知道了。
這里我用的是dotpeek查看編譯器生成的c#代碼。
這里順便想問下各位,有沒有mac下c#反編譯的工具。求推薦
所以我們在寫重載方法時,是不能以 object 和 dynamic 來區分的。
void DynamicMethod(object o); void DynamicMethod(dynamic d); // error 編譯器無法通過編譯:已經存在同名同形參的方法
如果說 dynamic 與 object 一樣,那么它與 DLR 又有什么關系呢?
其實微軟提供這么一個關鍵字,我認為是方便提供創建動態類型的快捷方式。而真正于動態類型密切相關的是命名空間System.Dynamic
下的類型。主要核心類DynamicObject,ExpandoObject,IDynamicMetaObjectProvider
,關于這三個類我們這節先不談。
DLR探秘
首先我們來大致了解C#4.0加入的重要功能 DLR,在編譯器中處于什么層次結構。
在這里我引用 https://www.codeproject.com/Articles/42997/NET-4-0-FAQ-Part-1-The-DLR 這片文章的一副結構圖的意思
動態編程 = CLR + DLR
這足以說明 DLR 在C#中的位置,雖然名字與CLR只有一個字母之差,但是它所處的層次其實是在CLR之上的。我們知道編譯器將我們寫的代碼轉換成IL,然后經由CLR轉換成本地代碼交由CPU執行可執行程序。那么實際上,DLR 是在編譯期間和運行期做了大量工作。最后還是會將C#代碼轉換成CLR靜態語言,然后再經由 CLR 將代碼轉換成本地代碼執行(如調用函數等)。
現在我們來簡要介紹一下DLR在編譯期間做了什么。
到這里就不得不以例子來做說明了,我們就上面的例子稍加改造一下:
// program.cs dynamic x = "marson shine"; string v = x.Substring(6); Console.WriteLine(v);
為了節省篇幅,我簡化并改寫了難看的變量命名以及不必要的注釋。生成的代碼如下:
object obj1 = (object) "marson shine"; staticCallSite1 = staticCallSite1 ?? CallSite<Func<CallSite, object, int, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.None, "Substring", (IEnumerable<Type>) null, typeof (Example), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.Constant, (string) null) })); object obj2 = ((Func<CallSite, object, int, object>) staticCallSite1.Target)((CallSite) staticCallSite1, obj1, 6); staticCallSite2 = staticCallSite2 ?? CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "WriteLine", (IEnumerable<Type>) null, typeof (Example), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null) })); ((Action<CallSite, Type, object>) staticCallSite2.Target)((CallSite) staticCallSite2, typeof (Console), obj2);
上文的兩個變量staticCallSite1,staticCallSite2
是靜態變量,起到緩存的作用。
這里涉及到了DLR核心三個概念
總結
DLR運行過程我們總結起來就是,在運行時DLR利用編譯運行期間生成的表達式樹、調用點、綁定器代碼,以及緩存機制,我們就可以做到計算的重用來達到高性能。ASP.NET頁面緩存常見的4種方式
現在我們就知道了為什么DLR能干出與反射相同的效果,但是性能要遠比反射要高的原因了。
補充說明
剛看到評論里的同學提到了reflection與dynamic的性能測試比較,發現反射性能占據明顯的優勢。事實上,從那個例子來看,恰恰說明了DLR的問題。這里我先列出他的測試代碼
const int Num = 1000 * 100; { var mi = typeof(XXX).GetMethod("Go"); var go1 = new XXX(); for (int i = 0; i < Num; i++) { mi.Invoke(go1, null); } } { dynamic go1 = new XXX(); for (int i = 0; i < Num; i++) { go1.Go(); } }
在這個測試中,已經將反射出來的元數據信息緩存到局部變量 mi,所以在調用方法的時候,實際上用到的是已經緩存下來的 mi。那么在沒有緩存優勢的情況,說明DLR性能是不如 MethodInfo+Invoke
的。
其實在文章總結的時候也強調了,利用緩存機制達到多次重復計算的重用來提高性能
那么我們在看一個例子:
public void DynamicMethod(Foo f) { dynamic d = f; d.DoSomething(); } public void ReflectionMethod(Foo f) { var m = typeof(Foo).GetMethod("DoSomething"); m?.Invoke(f, null); }
方法 DoSomething 只是一個空方法。現在我們來看執行結果
// 執行時間 var f = new Foo(); Stopwatch sw = new Stopwatch(); int n = 10000000; sw.Start(); for (int i = 0; i < n; i++) { ReflectionMethod(f); } sw.Stop(); Console.WriteLine("ReflectionMethod: " + sw.ElapsedMilliseconds + " ms"); sw.Restart(); for (int i = 0; i < n; i++) { DynamicMethod(f); } sw.Stop(); Console.WriteLine("DynamicMethod: " + sw.ElapsedMilliseconds + " ms"); // 輸出 ReflectionMethod: 1923 ms DynamicMethod: 223 ms
這里我們就能明顯看出執行時間的差距了。實際上DLR的執行過程我用下面偽代碼表示
public void DynamicMethod(Foo f) { dynamic d = f; d.DoSomething(); } // 以下是DLR會生成大概的代碼 static DynamicCallSite fooCallSite; public void ReflectionMethod(Foo f) { object d = f; if(fooCallSite == null) fooCallSite = new DynamicCallSite(); fooCallSite.Invoke("Foo",d); }
編譯器在編譯上述方法DynamicMethod
時,會詢問一次這個調用點調用的方法的類型是否是同一個,如果是則直接將已經準備好的調用點 fooCallSite 進行調用,否則則像文章之前說的,會生成調用點,綁定器綁定成員信息,根據AST將表達式生成表達式樹,將這些都緩存下來。在進行計算(調用)。
正因為我們知道了DLR的一些內幕,所以我們自然也知道了注意該如何用 DLR,以及關鍵字 dynamic。比如我們現在知道了C#編譯器會將 dynamic 等同 object 對待。那么我們在使用的時候一定要注意不要被“莫名其妙”的被裝箱了,導致不必要的性能損失了。
至于 DLR 的應用,特別是結合動態語言進行編程,來達到靜態語言動態編程的目的。其實DLR剛出來之際,就有了如 IronPython 這樣的開源組件。這是另外一個話題,并且我們在做實際應用的情況也很少,所以就沒有展開來講了。
補充:
DLR主要提供以下三個功能:
1.語言實現服務提供語言的互操作性
2.動態語言運行時服務提供動態調用支持
3.公共腳本宿主
依托這些模塊,您可以非常輕松的做下面這些事
1.為您現有的.NET應用程序,加入腳本支持
2.為您現有的語言,提供動態知己
3.為任何對象提供動態操作支持
4.在您的架構中提供腳本語言.
參數資料:
https://www.codeproject.com/Articles/42997/NET-4-0-FAQ-Part-1-The-DLR《深入理解C#》
到此這篇關于一文帶你了解 C# DLR 的世界的文章就介紹到這了,更多相關C# DLR 內容請搜索億速云以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持億速云!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。