• c#3.0系列:Extension Method


    我们说在C#3.0中,引入了一些列新的特性,但是个人认为Extension Method这个特性是最爽的,最有创新的。
    它真正的解决了:在保持现有Type原封不动的情况下对其进行扩展,你可以在对Type的定义不做任何变动的情况下,为之添加所需的方法成员。下面我就来讲讲。

    C#3.X出来之前
    大家都知道javascript有个特新Prototype,它就如同C#3.X中的Extension Method。这里不多将了。
    我们主要看看.NET的实现。在C#3.X出来之前我们可以做到对Type进行扩展。
    interface的情况:

    1public interface IEmployee
    2{
    3        string Name getset; }
    4        int  Age{getset;}
    5        int add(int n);
    6}

    对这个Interface进行扩展,为之添加一个Add方法执行相关的运算。我们唯一的解决方案就是直接在这个Interface中添加一个Add成员。如上。实现了这个Interface的Type必须实现该Interface的所有方法。所以,我们添加了Add这个方法,将导致所有实现它的Type的重新定义和编译,在很多情况下,我们根本不需要这样。

    Class的情况:
    如果我们将一个class作为基类,在基类中添加一个Add Method,所有的Child Class都不会受到影响。但是在很多情况下,对于我们需要扩展的Interface或者是class,我们是完全不能做任何改动。比如,我们要对datagrid控件进行扩展。我们常用的方法就自定义一个Class去继承这个datagrid,将需要添加的成员定义在我们自己定义的Class中,这就是我们常说的自定义控件,如果对于一个Sealed Class又该如何呢?我们要求的是对这个不能变动的Type进行扩展,也就是使这个不能变动的Type的Instance具有我们添加的对象。

    如果听到这样的要求:我们要对一个Type或者Interface进行扩展,却不允许我们修改它。这个要求确实有点苛刻。但是c#3.x 中我们可以选择Extension Method。Extension Method本质上是在被扩展的对象实例上可以调用的静态函数,不是继承,所以不同于普通的成员函数,扩展函数不能直接访问被扩展对象的成员。只能通过该对象的实例来访问。

    C#3.X出来之后
    简单地说Extension Method是一个定义在Static Class的一个特殊的Static  Method。之所以说这个Static Method特别,是因为Extension Method不但能按照Static Method的语法进行调用,还能按照Instance Method的语法进行调用。
    我们还是先来看例子:

     public static class MyExtensionMethods
        
    {
            
    // this代表扩展方法应用于string类型上
             public static int ToInt32(this string s)
            
    {
                
    int i;
                Int32.TryParse(s, 
    out i);
                
    return i;
            }

        }

    public static void fnExtensionMethod()
            
    {
                
    string s = "27";
                
    // 使用string的ToInt32()扩展方法
                int i = s.ToInt32();
            }

    我们可以看看上面的例子,我们知道net framework 里string是个Sealed 类型,我们只能使用Extension Method来对其进行扩展。我们可以看看它的定义方式。ToInt32是一个Static方法。和一般的Static方法不同的是:在第一个参数前添加了一个this 关键字。这是在C# 3.0中定义Extension Method而引入的关键字。添加了这样一个关键字就意味着在调用该方法的时候这个标记有this的参数可以前置,从而允许我们向调用一般Instance Method的方式来调用这个Static Method。注意:需要在(只需要在)第一个参数前面使用this修饰。

    Extension Method IN CLR
    C# 3.0的这些新的特性大都影响Source被C# Compiler编译成Assembly这个阶段,换句话说,这些新特仅仅是Compiler的新特性而已。通过对Compiler进行修正,促使他将C# 3.0引入的新的语法编译成相对应的IL Code,从本质上看,这些IL Code 和原来的IL并没有本质的区别。所有当被编译生成成Assembly被CLR加载、执行的时候,CLR是意识不到这些新的特性的。
    我们先看看Extension Method与传统的Static Method有什么区别。

     1.method public hidebysig static int32 ToInt32(class ConsoleApplication1.person s) cil managed
     2{
     3    .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
     4    .maxstack 2
     5    .locals init (
     6        [0] int32 i,
     7        [1] int32 CS$1$0000,
     8        [2] int32 CS$0$0001)
     9    L_0000: nop 
    10    L_0001: ldarg.0 
    11    L_0002: callvirt instance int32 ConsoleApplication1.person::get_Age()
    12    L_0007: stloc.2 
    13    L_0008: ldloca.s CS$0$0001
    14    L_000a: call instance string [mscorlib]System.Int32::ToString()
    15    L_000f: ldloca.s i
    16    L_0011: call bool [mscorlib]System.Int32::TryParse(string, int32&)
    17    L_0016: pop 
    18    L_0017: ldloc.0 
    19    L_0018: stloc.1 
    20    L_0019: br.s L_001b
    21    L_001b: ldloc.1 
    22    L_001c: ret 
    23}

    24
    25 
    26
    27 
    28

    大家注意,这是Extension Method的IL,它与传统的Static Method最大的区别事在Extension Method多了这句:
    .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
    就是在ToInt32方法上添加一个Customer Attribute:System.Runtime.CompilerServices.ExtensionAttribute。这个
    属性是为了Extension Method的而定义的。
    我们再来看看,IL事如何实现Instance Method方式调用Extension Method的。

     1.method public hidebysig static void fnExtensionMethod() cil managed
     2{
     3    .maxstack 1
     4    .locals init (
     5        [0string s,
     6        [1] int32 i,
     7        [2] int32 j)
     8    L_0000: nop 
     9    L_0001: ldstr "27"
    10    L_0006: stloc.0 
    11    L_0007: ldloc.0 
    12    L_0008: call int32 ConsoleApplication1.MyExtensionMethods::ToInt32(string)
    13    L_000d: stloc.1 
    14    L_000e: ldstr "28"
    15    L_0013: call int32 ConsoleApplication1.MyExtensionMethods::ToInt32(string)
    16    L_0018: stloc.2 
    17    L_0019: ldloca.s i
    18    L_001b: call instance string [mscorlib]System.Int32::ToString()
    19    L_0020: call void [mscorlib]System.Console::WriteLine(string)
    20    L_0025: nop 
    21    L_0026: ret 
    22}

    23
    24

    通过上面的IL,我们看到调用的是ConsoleApplication1.MyExtensionMethods的ToInt32方法。
    我们基本上看出了Extension Method的本质。当Compiler对Adds方法的调用进行编译的过程的时候,它必须判断这个ToInt32方式是String已有的成员还是以Extension Method的方式定义。Extension Method的优先级是最低的,只有确定String中没有定义相应的ToInt32方法的时候,Compiler才会在引用的Namespace中查看这些Namespace中是否定义有对应的ToInt32 Extension Method的Static Class。找到后作进行相应的编译,否则出现编译错误。

    扩展接口类型
    看下面的例子

     1public static void fnIntExtensionMethod()
     2        {
     3
     4            Console.WriteLine("***** Extending an interface *****\n");
     5            // Call IMath members from MyCalc object.
     6            MyCalc c = new MyCalc();
     7            Console.WriteLine("1 + 2 = {0}", c.Add(12));
     8            Console.WriteLine("1 - 2 = {0}", c.Subtract(12));
     9            // Can also cast into IBasicMath to invoke extension.
    10            Console.WriteLine("30 - 9 = {0}", ((IMath)c).Subtract(309));
    11            // This would NOT work!
    12             //IMath itfBM = new IMath();
    13             //itfBM.Subtract(10, 10);
    14            Console.ReadLine();
    15
    16        }

    17
    18public static class MyExtensionMethods
    19    {
    20        // this代表扩展方法应用于string类型上
    21        // ToInt32()是将string类型转换为int类型的扩展方法
    22        public static int ToInt32(this string s)
    23        {
    24            int i;
    25            Int32.TryParse(s, out i);
    26            return i;
    27        }

    28
    29        public static int Subtract(this IMath itf ,int x, int y)
    30        {
    31            return x - y;
    32        }

    33
    34    }

    35public interface IMath
    36    {
    37        int Add(int x, int y);
    38    }

    39    
    40class MyCalc : IMath
    41    {
    42        public int Add(int x, int y)
    43        {
    44            return x + y;
    45        }

    46    }

    注意这里扩展时必须给出函数的实现,扩展接口后,显然不能直接在接口上调用这些扩展函数,只能理解为,所有继承该接口的对象新增加了这些扩展函数功能。

    注意事项:
    引用扩展函数
    必须引用定义扩展函数的命名空间,否则扩展函数不可用。
    智能提示
    Visual studio 的智能提示将扩展函数标记为向下的蓝色箭头。

    版权所有归"布衣软件工作者".未经容许不得转载.
  • 相关阅读:
    第06组Alpha冲刺(4/6)
    第06组Alpha冲刺(3/6)
    第06组Alpha冲刺(2/6)
    第06组 Alpha冲刺 (1/6)
    08-js函数
    07-数组
    06-js分支
    05-js运算符
    04-js变量
    03-css3D转换
  • 原文地址:https://www.cnblogs.com/gjcn/p/1148298.html
Copyright © 2020-2023  润新知