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 Cruise
Tom 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的时候并不会有任何的任何数据获取的过程,这个阶段的任务是创建一个同于获取数据的表达式。只要你真正所用到这个数据的时候,采用重数据源中通过你构建的表达式通过查询获取数据。

时间: 2024-09-30 23:58:35

Extension Method[下篇]的相关文章

扩展Unity3d 组件方法,简化API使用 - C#特性之 Extension Method

在日常使用Unity3d中,经常碰到一些简单操作但是代码却很长的问题,比如变换一个 GameObject的 Y 位置,会按照下面的写法: transform.localPosition = new Vector3 (transform.localPosition.x, transform.localPosition.y + 100, transform.localPosition.z); 转自http://blog.csdn.net/huutu http://www.thisisgame.com

Extension Method[上篇]

在C#3.0中,引入了一些列新的特性,比如: Implicitly typed local variable, Extension method,Lambda expression, Object initializer, Anonymous type, Implicitly typed array, Query expression, Expression tree. 个人觉得在这一系列新特性的,最具创新意义的还是Extension method,它从根本上解决了这样的问题:在保持现有Type

Swift protocol extension method is called instead of method implemented in subclass

protocol MyProtocol { func methodA() func methodB() } extension MyProtocol { func methodA() { print("Default methodA") } func methodB() { methodA() } } // Test 1 class BaseClass: MyProtocol { } class SubClass: BaseClass { func methodA() { print(

c#的Extension Method功能

要在C#中使用类型的拓展方法,可在一个类中中通过指定static的方法,将Type通过this方式作为第一个参数传入,如给Random增加一个NextFloat方法 public static class Extension { public static byte[] NextBytes(this Random r, int length) { var data = new byte[length]; r.NextBytes(data); return data; } public stati

C# 扩展方法Extension Method

C# .NET Framewoke 3.0就引入的新特性,提供了扩展.NET类方法的途径,可以增加代码的美观性! 编写扩展方法有下面几个要求: 扩展方法所在的类必须是全局的,不能是内部嵌套类, 扩展方法的类必须是静态类. 扩展方法必须是静态方法. 扩展方法的第一个参数的数据类型必须是要扩展类型且使用this关键字. 扩展方法定义: public static class CExLongMethed { //无参无返回值 public static void Print(this long l)

Extension method for type

扩展其实真的很简单 msdn是这样规定扩展方法的:"扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的. 它们的第一个参数指定该方法作用于哪个类型,并且该参数以 this 修饰符为前缀." 扩展方法的关键不在于定义所在的class的名字,关键在于扩展方法的第一个参数,以及所有class和扩展方法是否为static. 扩展方法的第一个参数指定这个扩展方法作用在的class. 比如我的例子中为string添加一个扩展方法 (ToIntExt), 所以第一个方法必须是string类

Extension Methods

Oftentimes you’ll find yourself using classes you can’t modify. Whether they’re basic data types or part of an existing framework, you’re stuck with the functions that are provided. That being said, C# provides a nifty trick to appending functions to

Extension Methods(扩展方法)

在 OOPL 中,有静态方法.实例方法和虚方法,如下: public sealed class String { public static bool  IsNullOrEmpty(string s) { // ... } public string Replace(string old, string new) { // ... } } public abstract class Stream { public virtual void WriteByte(byte value) { // .

C# Extension Methods

In C#, extension methods enable you to add methods to existing class without creating a new derived class. Extension methods 要求: Define a static class to contain extension method. This class must be visible to client code. Implement the extension met