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 的智能提示将扩展函数标记为向下的蓝色箭头。

posted on 2008-04-11 13:16  gjcn  阅读(3442)  评论(15编辑  收藏  举报

导航