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

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[下篇]相关推荐

  1. 深入理解C# 3.x的新特性(5):Object Initializer 和 Collection Initializer

    深入理解C# 3.x的新特性系列自开篇以后,已经有两个月了.在前面的章节中,我们先后深入讨论了C# 3.x新引入的一些列新特性:Anomynous Type.Extension Method.Lamb ...

  2. [原创]深入理解C# 3.x的新特性(3):从Delegate、Anonymous Method到Lambda Expression

    较之前一个版本,对于C# 3.x和VB 9来说,LINQ是最具吸引力的.基本上很多的新的特性都是围绕着LINQ的实现来设计的.借助Extension Method,我们可以为LINQ定义一系列的Ope ...

  3. 《深入理解C++11:C++ 11新特性解析与应用》——1.3 C++11特性的分类

    1.3 C++11特性的分类 从设计目标上说,能够让各个特性协同工作是设计C++11/0x中最为关键的部分.委员会总希望通过特性协作取得整体大于个体的效果,但这也是语言设计过程中最困难的一点.因此相比 ...

  4. 《深入理解C++11:C++ 11新特性解析与应用》——导读

    前 言 为什么要写这本书 相比其他语言的频繁更新,C++语言标准已经有十多年没有真正更新过了.而上一次标准制定,正是面向对象概念开始盛行的时候.较之基于过程的编程语言,基于面向对象.泛型编程等概念的C ...

  5. 《深入理解C++11:C++ 11新特性解析与应用》——2.4 宏__cplusplus

    2.4 宏__cplusplus 类别:部分人 在C与C++混合编写的代码中,我们常常会在头文件里看到如下的声明: #ifdef __cplusplus extern "C" { ...

  6. 《深入理解C++11:C++ 11新特性解析与应用》——3.2 委派构造函数

    3.2 委派构造函数 类别:类作者 与继承构造函数类似的,委派构造函数也是C++11中对C++的构造函数的一项改进,其目的也是为了减少程序员书写构造函数的时间.通过委派其他构造函数,多构造函数的类编写 ...

  7. [C++11] 新特性总结

    C++11新增加了哪些新特性?一般而言,大概有以下四个方面: "语法糖":nullptr, auto自动类型推导,范围for循环,初始化列表, lambda表达式等 右值引用和移动 ...

  8. [react] 请描述下你对react的新特性Hooks的理解?它有哪些应用场景?

    [react] 请描述下你对react的新特性Hooks的理解?它有哪些应用场景? 在 React 中使用 class 继承 React.Component,就可以在类里面使用各种各样的钩子函数,比如 ...

  9. accept 返回0_从0开始理解Vite的主要新特性(一)

    此文已同步到公众号[因卓诶]以及因卓诶博客: 从0开始理解Vite的主要新特性(一) - 因卓诶-爱分享爱原创的技术博客 ~ 个人博客​www.yinzhuoei.com vite这个工具确实尤大在微 ...

最新文章

  1. A. 位运算符的应用---管理一组事务的开关状态
  2. python如何做散点图-Python-如何为散点图制作动画?
  3. HTTP之Content-Security-Policy的使用(C++ Qt框架实现)
  4. 10余万行C代码开源之后,我被震惊了。。。
  5. java集合框架(hashSet自定义元素是否相同,重写hashCode和equals方法)
  6. 纽微特记事:有了工作产出,领导吓坏了
  7. and design pro实现打印电子面单(菜鸟物流-可批量打印)
  8. [TJOI2019]唱、跳、rap和篮球
  9. ftp服务器文件不显示,ftp服务器不显示文件夹大小
  10. 火箭发射:点击率预估界的“神算子”是如何炼成的?...
  11. Arduino智能闹钟设计(8x8矩阵键盘+LCD显示)
  12. (原创)自编protel99se鼠标增强工具,支持dxp2004
  13. SUSE史上首位女性CEO Melissa Di Donato,不止有“三把火”
  14. 知识图谱与认知智能--肖仰华
  15. SQL Server中对比表数量,索引数量及procedure数量
  16. MATLAB | 官方自带的绘图代码生成功能咋用
  17. 【如何系统的学习it技术】
  18. 综武大唐:从剑圣收徒开始(二)
  19. 初中数学老师计算机培训反思,初中数学教师培训研修总结范文(精选5篇)
  20. centos,debian,ubuntu系统国内源下载地址

热门文章

  1. LeetCode 375. 猜数字大小 II(DP)
  2. 程序员面试金典 - 面试题 17.17. 多次搜索(Trie树)
  3. mysql建表_128、mysql建表和简单sql
  4. html设置div页面最底,使用css让大图片不超过网页宽度
  5. Android设置text按钮,安卓基础控件使用(TextView、Button、ImageView、EditText)
  6. python网络编程内容_Python网络编程
  7. 我在斯坦福做科研的碎碎念
  8. 掌握神经网络,我应该学习哪些至关重要的知识点?
  9. 斯坦福大学NLP公开课CS224n上映啦!华人助教陪你追剧
  10. 科普一下人工智能领域的研究方向