深入理解C# 3.x的新特性(2):Extension Method[下篇]
四、Extension Method的本質
通過上面一節的介紹,我們知道了在C#中如何去定義一個Extension Method:它是定義在一個Static class中的、第一個Parameter標記為this關鍵字的Static Method。在這一節中,我們來進一步認識Extension Method。
和C# 3.0的其他新特性相似,Extension Method僅僅是C#這種.NET Programming Language的新特性而已。我們知道,C#是一種典型的編譯型的語言,我們編寫的Source Code必須先經過和C# Compiler編譯成Assembly,才能被CLR加載,被JIT 編譯成Machine Instruction并最終被執行。C# 3.0的這些新的特性大都影響Source被C# Compiler編譯成Assembly這個階段,換句話說,這些新特僅僅是Compiler的新特性而已。通過對Compiler進行修正,促使他將C# 3.0引入的新的語法編譯成相對應的IL Code,從本質上看,這些IL Code 和原來的IL并沒有本質的區別。所有當被編譯生成成Assembly被CLR加載、執行的時候,CLR是意識不到這些新的特性的。
從Extension Method的定義我們可看出,Extension Method本質上是一個Static Method。但是我們往往以Instance Method的方式進行調用。C# Compiler的作用很明顯:把一個以Instance Method方式調用的Source Code編譯成的于對應于傳統的Static Method調用的IL Code。
雖然Extension Method本質上僅僅是一個Static Class的Static Method成員,但是畢竟和傳統的Static Method有所不同:在第一個Parameter前加了一個this關鍵字。我們現在來看看他們之間的細微的差異。我們先定義一個一般的Static Method:
public?static?Vector?Adds(Vector?v,?Vector?v1){
??return?new?Vector?{?X?=?v.X?+?v1.X,?Y?=?v.Y?+?v1.Y?};
}
注:Vector的定義參見《深入理解C# 3.0的新特性(2):Extension Method - Part I》。
我們來看看通過Compiler進行編譯生成的IL:
.method?private?hidebysig?static?void??Main(string[]?args)?cil?managed{
??.entrypoint
??//?Code?size???????50?(0x32)
??.maxstack??2
??.locals?init?([0]?class?Artech.ExtensionMethod.Vector?v,
???????????[1]?class?Artech.ExtensionMethod.Vector?'<>g__initLocal0')
??IL_0000:??nop
??IL_0001:??newobj?????instance?void?Artech.ExtensionMethod.Vector::.ctor()
??IL_0006:??stloc.1
??IL_0007:??ldloc.1
??IL_0008:??ldc.r8?????1.
??IL_0011:??callvirt???instance?void?Artech.ExtensionMethod.Vector::set_X(float64)
??IL_0016:??nop
??IL_0017:??ldloc.1
??IL_0018:??ldc.r8?????2.
??IL_0021:??callvirt???instance?void?Artech.ExtensionMethod.Vector::set_Y(float64)
??IL_0026:??nop
??IL_0027:??ldloc.1
??IL_0028:??stloc.0
??IL_0029:??ldloc.0
??IL_002a:??ldloc.0
??IL_002b:??call???????class?Artech.ExtensionMethod.Vector?Artech.ExtensionMethod.Extension::Adds(class?Artech.ExtensionMethod.Vector,
class?Artech.ExtensionMethod.Vector)
??IL_0030:??stloc.0
??IL_0031:??ret
}?//?end?of?method?Program::Main
對了解IL的人來說,對上面的IL code應該很容易理解。
我們再來看看對于通過下面的方式定義的Extension Method:
public?static?class?Extension????{
?????????public?static?Vector?Adds(this?Vector?v,?Vector?v1)
????????{
????????????return?new?Vector?{?X?=?v.X?+?v1.X,?Y?=?v.Y?+?v1.Y?};
????????}
}
對于得IL如下:
.method?public?hidebysig?static?class?Artech.ExtensionMethod.Vector?Adds(class?Artech.ExtensionMethod.Vector?v,
class?Artech.ExtensionMethod.Vector?v1)?cil?managed
{
??.custom?instance?void?[System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()?=?(?01?00?00?00?)?
??//?Code?size???????53?(0x35)
??.maxstack??3
??.locals?init?([0]?class?Artech.ExtensionMethod.Vector?'<>g__initLocal0',
???????????[1]?class?Artech.ExtensionMethod.Vector?CS$1$0000)
??IL_0000:??nop
??IL_0001:??newobj?????instance?void?Artech.ExtensionMethod.Vector::.ctor()
??IL_0006:??stloc.0
??IL_0007:??ldloc.0
??IL_0008:??ldarg.0
??IL_0009:??callvirt???instance?float64?Artech.ExtensionMethod.Vector::get_X()
??IL_000e:??ldarg.1
??IL_000f:??callvirt???instance?float64?Artech.ExtensionMethod.Vector::get_X()
??IL_0014:??add
??IL_0015:??callvirt???instance?void?Artech.ExtensionMethod.Vector::set_X(float64)
??IL_001a:??nop
??IL_001b:??ldloc.0
??IL_001c:??ldarg.0
??IL_001d:??callvirt???instance?float64?Artech.ExtensionMethod.Vector::get_Y()
??IL_0022:??ldarg.1
??IL_0023:??callvirt???instance?float64?Artech.ExtensionMethod.Vector::get_Y()
??IL_0028:??add
??IL_0029:??callvirt???instance?void?Artech.ExtensionMethod.Vector::set_Y(float64)
??IL_002e:??nop
??IL_002f:??ldloc.0
??IL_0030:??stloc.1
??IL_0031:??br.s???????IL_0033
??IL_0033:??ldloc.1
??IL_0034:??ret
}?//?end?of?method?Extension::Adds
通過比較,我們發現和上面定義的一般的Static Method生成的IL唯一的區別就是:在Adds方法定義最開始添加了下面一段代碼:
.custom?instance?void?[System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()?=?(?01?00?00?00?)?這段添加的IL代碼很明顯,就是在Adds方法上添加一個Customer Attribute:System.Runtime.CompilerServices.ExtensionAttribute。ExtensionAttribute具有如下的定義:
[AttributeUsage(AttributeTargets.Method?|?AttributeTargets.Class?|?AttributeTargets.Assembly)]public?sealed?class?ExtensionAttribute?:?Attribute
{
}
所以下面Extension Method的定義
public?static?Vector?Adds(this?Vector?v,?Vector?v1){
????????????return?new?Vector?{?X?=?v.X?+?v1.X,?Y?=?v.Y?+?v1.Y?};
}
和下面的定義是等效的
[ExtensionAttribute]public?static?Vector?Adds(Vector?v,?Vector?v1)?
{
????????????return?new?Vector?{?X?=?v.X?+?v1.X,?Y?=?v.Y?+?v1.Y?};
}
但是,System.Runtime.CompilerServices.ExtensionAttribute和其他Custom Attribute不一樣,因為它是為了Extension Method的而定義的,我們只能通過添加this Key word的語法來定義Extension Method。所以當我們將System.Runtime.CompilerServices.ExtensionAttribute直接運用到Adds方法會出現下面的Compile Error:
Do?not?use?'System.Runtime.CompilerServices.ExtensionAttribute'.?Use?the?'this'?keyword?instead.上面我們比較了Extension Method本身IL和一般Static Method IL,現在我們看看當我們以Instance Method方式調用Extension Method的IL。假設我們通過下面的方式調用Adds。
class?Program????{
????????static?void?Main(string[]?args)
????????{
?var?v?=?new?Vector?{?X?=?1,?Y?=?2?};
???????????v?=?v.Adds(v);
????????}
}
下面是Main Method的IL:
.method?private?hidebysig?static?void??Main(string[]?args)?cil?managed{
??.entrypoint
??//?Code?size???????50?(0x32)
??.maxstack??2
??.locals?init?([0]?class?Artech.ExtensionMethod.Vector?v,
???????????[1]?class?Artech.ExtensionMethod.Vector?'<>g__initLocal0')
??IL_0000:??nop
??IL_0001:??newobj?????instance?void?Artech.ExtensionMethod.Vector::.ctor()
??IL_0006:??stloc.1
??IL_0007:??ldloc.1
??IL_0008:??ldc.r8?????1.
??IL_0011:??callvirt???instance?void?Artech.ExtensionMethod.Vector::set_X(float64)
??IL_0016:??nop
??IL_0017:??ldloc.1
??IL_0018:??ldc.r8?????2.
??IL_0021:??callvirt???instance?void?Artech.ExtensionMethod.Vector::set_Y(float64)
??IL_0026:??nop
??IL_0027:??ldloc.1
??IL_0028:??stloc.0
??IL_0029:??ldloc.0
??IL_002a:??ldloc.0
??IL_002b:??call???????class?Artech.ExtensionMethod.Vector?Artech.ExtensionMethod.Extension::Adds(class?Artech.ExtensionMethod.Vector,
class?Artech.ExtensionMethod.Vector)
??IL_0030:??stloc.0
??IL_0031:??ret
}?//?end?of?method?Program::Main
通過上面的IL,我們看到調用的是Artech.ExtensionMethod.Extension的Adds方法。
IL_002b:??call?class?Artech.ExtensionMethod.Vector?Artech.ExtensionMethod.Extension::Adds(class?Artech.ExtensionMethod.Vector,class?Artech.ExtensionMethod.Vector)
通過對IL的分析,我們基本上看出了Extension Method的本質。我們再來簡單描述一下對Compiler的編譯過程:當Compiler對Adds方法的調用進行編譯的過程的時候,它必須判斷這個Adds方式是Vector Type的成員還是以Extension Method的方式定義。Extension Method的優先級是最低的,只有確定Vector中沒有定義相應的Adds方法的時候,Compiler才會在引用的Namespace中查看這些Namespace中是否定義有對應的Adds Extension Method的Static Class。找到后作進行相應的編譯,否則出現編譯錯誤。
五、一個完整的Extension Method的Sample
在介紹了Extension Method的本質之后,我們通過一個相對完整的Sample進一步了解Extension Method的運用,通過這個Sample,我們還可以粗略了解LINQ的原理。
C# 3.0為LINQ定義了一系列的Operator:select, from,where,orderby..., 促使我們按照OO的方式來處理各種各樣的數據,比如XML,Relational DB Data,C#中IEnumeratable<T> Object。比如:
var?names?=?new?List<string>?{?"Tom?Cruise",?"Tom?Hanks",?"Al?Pacino",?"Harrison?Ford"?};var?result?=?names.Where(name?=>?name.StartsWith("Tom"));
foreach(var?name?in?result)
{
??????Console.WriteLine(name);
}
我們通過上面的Code,從一系列的姓名列表中("Tom Cruise", "Tom Hanks", "Al Pacino", "Harrison Ford")篩選名字(First Name)為Tom的姓名。通過Where Operator,傳入一個以Lambda Expression表示的篩選條件(name => name.StartsWith("Tom"))。Where Operator就是通過Extension Method的方式定義的。
在這里提供的Sample就是定義一個完成Where Operator相同功能的Operator,我們把這個Operator起名為When。
using?System;using?System.Collections.Generic;
using?System.Linq;
using?System.Text;
using?System.Collections;
namespace?Artech.ExtensionMethod
{
????public?delegate?TResult?Function<Tparam,?TResult>(Tparam?param);
????public?static?class?Extension
????{
????????public?static?IEnumerable<TSource>?When<TSource>(this?IEnumerable<TSource>?source,?Function<TSource,?bool>?predicate)
????????{
????????????return?new?WhenEnumerator<TSource>(source,?predicate);
????????}??
????}
????public?class?WhenEnumerator<TSource>?:?IEnumerable<TSource>,?IEnumerator<TSource>
????{
????????private?IEnumerable<TSource>?_source;
????????private?Function<TSource,?bool>?_predicate;
????????private?IEnumerator<TSource>?_sourceEnumerator;
????????public?WhenEnumerator(IEnumerable<TSource>?source,?Function<TSource,?bool>?predicate)
????????{
????????????this._source?=?source;
????????????this._predicate?=?predicate;
????????????this._sourceEnumerator?=?this._source.GetEnumerator();
????????}
????????IEnumerable?Members#region?IEnumerable<TSource>?Members
????????public?IEnumerator<TSource>?GetEnumerator()
????????{
????????????return?new?WhenEnumerator<TSource>(this._source,?this._predicate);
????????}
????????#endregion
????????IEnumerable?Members#region?IEnumerable?Members
????????IEnumerator?IEnumerable.GetEnumerator()
????????{
????????????throw?new?Exception("The?method?or?operation?is?not?implemented.");
????????}
????????#endregion
????????IEnumerator?Members#region?IEnumerator<TSource>?Members
????????public?TSource?Current
????????{
????????????get?{?return?this._sourceEnumerator.Current;?}
????????}
????????#endregion
????????IDisposable?Members#region?IDisposable?Members
????????public?void?Dispose()
????????{
????????????//throw?new?Exception("The?method?or?operation?is?not?implemented.");
????????}
????????#endregion
????????IEnumerator?Members#region?IEnumerator?Members
????????object?IEnumerator.Current
????????{
????????????get
????????????{
????????????????return?this._sourceEnumerator.Current;
????????????}
????????}
????????public?bool?MoveNext()
????????{
????????????if?(!this._sourceEnumerator.MoveNext())
????????????{
????????????????return?false;
????????????}
????????????while?(!this._predicate(this._sourceEnumerator.Current))
????????????{
????????????????if?(!this._sourceEnumerator.MoveNext())
????????????????{
????????????????????return?false;
????????????????}
????????????}
????????????return?true;
????????}
????????public?void?Reset()
????????{
????????????this._sourceEnumerator.Reset();
????????}
????????#endregion
????}
}
我們來看看我們新的LINQ Operator:When的定義。我首先定義了一個Generic Delegate:Function。實際上他定義了一個一元函數y?=?f(x),TParam和TResult為參數和返回值得類型。?
public?delegate?TResult?Function<Tparam,?TResult>(Tparam?param);接著在Static Class Extesnion中定義了Extension Method:When。該方法包含兩個參數,其中一個是執行篩選的數據源,另一個是用于判斷數據源每個對象是否滿足你所定義的篩選條件的斷言。返回一個我們自定義的、實現了IEnumerable的WhenEnumerator對象。
public?static?class?Extension????{
????????public?static?IEnumerable<TSource>?When<TSource>(this?IEnumerable<TSource>?source,?Function<TSource,?bool>?predicate)
????????{
????????????return?new?WhenEnumerator<TSource>(source,?predicate);
????????}?
????}
WhenEnumerator的定義是實現When Extension Method的關鍵,我們現在著重來介紹它的具體實現。WhenEnumerator實現了Interface Enumerable<T>,為了簡單,我們也它對應的Enumerator的實現也定義在同一個Class中,所以WhenEnumerator實現了兩個Interface:IEnumerable<TSource>, IEnumerator<TSource>。?
以下3個成員分別代表:用于執行篩選的數據源、用于判斷是否滿足篩選條件的斷言以及數據源的Enumerator對象。
private?IEnumerable<TSource>?_source;private?Function<TSource,?bool>?_predicate;
private?IEnumerator<TSource>?_sourceEnumerator;
通過返回一個WhenEnumerator對象,實現了IEnumerable<TSource>的GetEnumerator()方法。?
????????public?IEnumerator<TSource>?GetEnumerator()????????{
????????????return?new?WhenEnumerator<TSource>(this._source,?this._predicate);
????????}
對于另一個Interface IEnumerator<TSource>,直接調用數據源的Enumerator的同名方法實現了Current,和Reset()。對于MoveNext()則通過如下的方式實現:把當前的位置設置在下一個滿足篩選條件的Element上。
public?bool?MoveNext()????????{
????????????if?(!this._sourceEnumerator.MoveNext())
????????????{
????????????????return?false;
????????????}
????????????while?(!this._predicate(this._sourceEnumerator.Current))
????????????{
????????????????if?(!this._sourceEnumerator.MoveNext())
????????????????{
????????????????????return?false;
????????????????}
????????????}
????????????return?true;
????????}
到現在為止,這個新的LINQ Operator被創建,現在我們可以按照使用Where operator的方式來調用When。
我們可以通過Delegate的方式來使用When Operator:
class?Program????{
????????static?void?Main()
????????{
????????????var?names?=?new?List<string>?{?"Tom?Cruise",?"Tom?Hanks",?"Al?Pacino",?"Harrison?Ford"?};
????????????var?result?=?names.When(delegate(string?name)?{?return?name.StartsWith("Tom");?});
????????????foreach?(var?name?in?result)
????????????{
????????????????Console.WriteLine(name);
????????????}
????????}
}
輸出結果:
Tom?CruiseTom?Hanks
我們也可以通過Lambda Expression的方式來使用When Operator:
static?void?Main()????????{
????????????var?names?=?new?List<string>?{?"Tom?Cruise",?"Tom?Hanks",?"Al?Pacino",?"Harrison?Ford"?};
????????????var?result?=?names.When(name=>name.StartsWith("Tom"));
????????????foreach?(var?name?in?result)
????????????{
????????????????Console.WriteLine(name);
????????????}
????????}
顯然這種方式更簡潔。?
Deferred Evaluation
對于LINQ,有一個非常重要的特征:Deferred Evaluation。在了解這個特征之前,我們來看一個例子:
static?void?Main(){
????????????var?names?=?new?List<string>?{?"Tom?Cruise",?"Tom?Hanks",?"Al?Pacino",?"Harrison?Ford"?};
????????????var?result1?=?names.When(name=>name.StartsWith("Tom"));
????????????names[0]?=?"Stephen?Chou";
????????????var?result2?=?names.When(name?=>?name.StartsWith("Tom"));
????????????foreach?(var?name?in?result1)
????????????{
????????????????Console.WriteLine(name);
????????????}
????????????foreach?(var?name?in?result2)
????????????{
????????????????Console.WriteLine(name);
????????????}
}
運行程序,你會發現兩個foreach loop顯示的結果都是一樣的:Tom Hanks。為什么result1實在第一個Element被改動之前返回的,但我們最終輸出的結果卻反映的是改動之后的數據源。通過我們上面的定義,你很容易得到答案。在這里我要說的是LINQ的一個重要的特性Deferred Evaluation:在調用Operator的時候并不會有任何的任何數據獲取的過程,這個階段的任務是創建一個同于獲取數據的表達式。只要你真正所用到這個數據的時候,采用重數據源中通過你構建的表達式通過查詢獲取數據。
C# 3.x相關內容:
[原創]深入理解C# 3.x的新特性(1):Anonymous Type
[原創]深入理解C# 3.x的新特性(2):Extension Method - Part I
[原創]深入理解C# 3.x的新特性(2):Extension Method - Part II
[原創]深入理解C# 3.x的新特性(3):從Delegate、Anonymous Method到Lambda Expression
[原創]深入理解C# 3.x的新特性(4):Automatically Implemented Property
[原創]深入理解C# 3.x的新特性(5):Object Initializer 和 Collection Initializer
轉載于:https://www.cnblogs.com/artech/archive/2007/07/19/823847.html
總結
以上是生活随笔為你收集整理的深入理解C# 3.x的新特性(2):Extension Method[下篇]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 4.8网络层设备
- 下一篇: 牛客18985 数字权重 (数学,快速幂