解析C# CLR的15個細節(jié)
本文整理了關(guān)于C# CLR的15個知識點。這些都是最為基本的知識,但由于現(xiàn)在大家對CLR還不是很了解,所以看起來會有一絲不理解,還是希望能給大家?guī)韼椭?/P>
1、C# CLR之foreach的性能問題
foreach(string s in rows) { foo(s); }的實現(xiàn)是:
- IEnumerator e = rows.GetEnumerator();
- try {
- string s;
- while (e.MoveNext()) {
- s = (String) e.Current;
- foo(s);
- }
- }
- finally {
- IDisposable d = e as IDisposable;
- if (d != null) d.Dispose();
- }
每一步都調(diào)用了e.MoveNext()和e.Current兩個方法;而大多數(shù)時候,完全有可能優(yōu)化為一次調(diào)用。顯然這對性能是有影響的。雖然foreach對于數(shù)組作了單獨的優(yōu)化(編譯成for循環(huán)),但這還是值得注意的。
那么,怎么做比較快?
對于List等Collection,可以用ForEach(Action
LINQ追求compatiblity,而不是performance。因此LINQ的實現(xiàn)完全采用了foreach。值得注意。
2、C# CLR之yield的實現(xiàn)原理
實現(xiàn)一個支持IEnumerable的對象時,一般會用到y(tǒng)ield關(guān)鍵字,這樣foreach遍歷這個對象時,可以做到lazy evaluation。例如:
- class MyCollection: IEnumerable<char>
- {
- private string s; ...
- public IEnumerable<char> GetEnumerator()
- {
- for (int i=0; i<s.Length; i++)
- {
- yield return s[i];
- }
- }
- }
執(zhí)行到y(tǒng)ield時函數(shù)返回,下次調(diào)用時,接著上次運行的位置繼續(xù)運行。這個continuation的效果是怎么做的呢?
包含yield的函數(shù)都會被編譯器做成一個狀態(tài)機。每調(diào)一次,就接著上次的狀態(tài)繼續(xù)運行。簡單有效啊。我一直以為要有什么特殊的辦法呢。
3、C# CLR之exception handling的實現(xiàn)決定了throw的performance較差。
可以用Int32.TryParse代替try{Int32.Parse…}catch{…},稍快一點。類似地建議使用Dictionary.TryGetValue。
4、C# CLR之.Net CLR執(zhí)行引擎對應(yīng)于MSCorWks.dll和MSCorEE.dll這兩個文件。
5、C# CLR之.Net 3.0, 3.5沒有對CLR作任何修改。
所有增加的東西(比如LINQ)都是syntactic sugar,只改了C#編譯器而已。
6、C# CLR之AppDomain
如果把.Net虛擬機看成一個虛擬操作系統(tǒng),AppDomain的概念則類似于操作系統(tǒng)中的進程。
可以用代碼創(chuàng)建一個AppDomain,然后動態(tài)加載/卸載assembly,還可以設(shè)置權(quán)限,相當于提供了一個沙箱。
跨AppDomain的調(diào)用類似于RPC。
調(diào)用某個AppDomain內(nèi)部的obj.foo(x)時,.net會自動幫你做出一個proxy object,你所調(diào)用的obj其實是一個proxy object。傳給foo的參數(shù)x會先被被marshal,以保證AppDomain被安全隔離。
誰用AppDomain?SQL Server用這個技術(shù)實現(xiàn)managed存儲過程。IIS會把不同的Web Application放在不同的AppDomain里,以實現(xiàn)動態(tài)裝卸。
7、C# CLR之動態(tài)載入Assembly的陷阱
Sytem.Reflection.Assembly.LoadFrom(pathName)并不會載入pathName所指定的dll,而是看看pathName那個dll的名字、版本,然后到系統(tǒng)默認位置去找。(陷阱?。?/P>
8、C# CLR之C#里用reflection創(chuàng)建一個新對象
用Activator.CreateInstance。(奇怪的名字啊。)
9、C# CLR之C#泛型之“where”
可以用“where”來限定T的接口。例如
static T min
不寫where的話,就不能調(diào)arg1.CompareTo(arg2)。
為啥不把T換成IComparable?一是為保證arg1, arg2一定是同一個類型,二是泛型的效率更高。(JIT會為不同類型的T各生成一份native code,從而避免了boxing)
更多where的細節(jié):
* 要想調(diào)T t1 = new T(),必須聲明where T: new()或者where T: struct
* 要寫T t2 = null,必須聲明where T: class
* T z = default(T)是一個特殊的用法,會把T的每個bit都置為0。
* 假設(shè)定義了Foo
* if (x==y)不行,除非寫了where T: baseclass。(這里我也沒理解為啥。。。>_<好像說是不知道應(yīng)該用reference比較還是value比較?)
10、C# CLR之匿名函數(shù)的背后。。。
在C# 2.0以后可以用匿名的delegate,如ThreadPool.QueueWorkItem(delegate (Object obj) { Console.WriteLine(obj); })
但編譯器的實現(xiàn)會帶來一點點overhead,會生成一個小小的靜態(tài)WaitCallback對象,可以用Reflector看生成的代碼。(不要打開Reflector的optimization,否則就看不到了)
如果是自己寫的話,可以選擇每次動態(tài)建立一個WaitCallback對象然后銷毀。當然這樣做性能可能差一些,但這里的idea是:編譯器會自動做一些事,但不一定是你所希望的。在使用這些高級feature前,最好先搞清楚背后發(fā)生了什么。
另一個細節(jié):如果匿名函數(shù)中使用了外層函數(shù)的局部變量(即所謂的function closure),會導致創(chuàng)建額外的shared-state object,把用到的局部變量做成一個新對象傳給匿名函數(shù)。
上述描述同樣適用于lambda函數(shù)。因為C#的lambda函數(shù)就是匿名函數(shù),改了改語法而已。
11、C# CLR之Nullable type
雖然C#要求value type的值不能是null,但寫數(shù)據(jù)庫程序時經(jīng)常遇到某個值是null的情況。為此,C#2.0引入了Nullable type。例如,int? x = null。
int? x其實就是一個縮寫,等價于Nullable
這個小改動的實現(xiàn)其實很麻煩,需要修改CLR。為什么?因為原先的x是一個value type,現(xiàn)在則變成了一個object,看這個:
- void M(Object o)
- {
- if (o=null) {Bar();}
- }
- void F()
- {
- int? x = null;
- M(x);
- }
如果CLR不專門做修正的話,上面的Bar()不會被執(zhí)行。(思考題:想一想為什么~)
另外,C#還引入了一個默認值運算符“??”,稱為null-coalescing operator。
一句話,x ?? value是 (x==null) ? value: x的簡寫。
12、C# CLR之屬性(property)的簡單聲明
- public int x {get; private set;}
是個很好用的句式。
注意,
- public int x {get;}
是錯誤的,不能通過編譯。
13、C# CLR之Extension method
- //Extension method
- static class MyExtMethods
- {
- static public GetFirstLetter(this string s) {return s[0];}
- }
然后就可以用string s = “hello”; char ch = s.GetFirstLetter()了。
原理很簡單,編譯器把上面那句話翻譯成MyExtMethods.GetFirstLetter(s)。LINQ就用到了這個技術(shù)。
14、C# CLR之匿名類型的背后。。。
- var o = new {name = “Xiangpeng”, id = 123 };
在這背后是編譯器生成的一個匿名類,包含了兩個只讀屬性,形如public int id { get {return _id;} }為什么不做成可讀寫的呢?
很微妙。匿名類自動生成了GetHashCode(),返回的是對所有屬性的hash code做XOR的結(jié)果。如果允許修改屬性值,那么Hash code的值就會變化;而這個可能會出問題~保險起見,只讀吧。
15、C# CLR之每個thread占1M物理內(nèi)存
在Win32編程中thread的1M stack空間是Reserve的,直到真正用時才占用物理內(nèi)存;而在.net中,這1M空間直接被commit。
還好,可以在新建thread時指定stack size。不過這也比較危險,設(shè)小了怕不夠。實際上,最好盡量避免創(chuàng)建thread——太多的thread要么導致CPU競爭和context switch,要么都block著浪費內(nèi)存。建議是:能用ThreadPool就用ThreadPool。
以上就是對C# CLR的比較介紹。
【編輯推薦】


2024-06-24 03:00:00
2024-03-20 10:59:37
2009-09-14 18:34:32
2009-08-27 17:40:21
2009-09-17 16:41:12
2009-09-01 10:28:38




