Artech

Develop every application as an art using the most suitable technologies!

常用链接

统计

积分与排名

CnBlogs

专家的Blog|主页

最新评论

[原创]深入理解C# 3.x的新特性(2):Extension Method - Part II

四、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 ([
0class Artech.ExtensionMethod.Vector v,
           [
1class 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 ([
0class Artech.ExtensionMethod.Vector '<>g__initLocal0',
           [
1class 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 ([
0class Artech.ExtensionMethod.Vector v,
           [
1class 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