C#2008语言特征3----扩展方法

来源:互联网 发布:生死狙击刷枪刷枪软件 编辑:程序博客网 时间:2024/04/30 11:32

 

现在假设你从别处得到一个类库,发现这个类库跟你现在做得项目很相近,也就是说这个类库对你十分有用,于是在项目中你使用了这个类库。可是突然有一天,你发现这个类库的功能不能完全满足你的需要,你需要添加新的功能,那么现在你有两个办法可以解决这个问题。

第一种解决问题的方案是在自己的类中定义一个新的方法去实现扩展功能。比如,现在你使用的类是一个描述手机的类,原来的手机类中没有定时关机功能,你现在需要给你开发的项目中添加该功能。于是写下如下类似的代码。

TrunOffByTime(TelePhone t,Time time)

{

    if(Current==time)

         t.turnoff();

}

然后在你需要的地方调用这个方法。可是这种使用方法总是让人感到有点别扭,因为定时关机毕竟是手机的一项功能,按照面向对象程序设计的思想,应该使用t. TrunOffByTime(time )这样的方式。

第二种解决问题的方案是从原有的类中派生出一个新的类,给派生类添加自己需要的功能。这种方法比较好,可是有时候,你会发现原有的类被写成了sealed类型。你没有办法继承这个类。即使你继承了这个类,也会发现使用这种方式扩展仍然不能使定义为原来类型的实例对象使用新扩展的方法,所以它也可能不是一个很好的解决方案。

现在终于轮到扩展方法登场了。扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。通俗一点的说,现在可以在不动原有类型的前提下,往这些类型中添加自己的方法。从例子StringToInt32来看。这个例子是把一个数字形式的字符串转换为整型,然后乘以2输出出来。传统的写法:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace StringToInt32

{

    class Program

    {

        static void Main(string[] args)

        {

            string str_num = "123";

            int num_str = Int32.Parse(str_num);

            Console.WriteLine((num_str * 2).ToString());

            Console.ReadKey();

        }

    }

}

看到把字符串转换为整型的方法Int32.Parse(str_num)的使用很别扭能不能让类型string具有ToInt32()这样的方法呢?看一下下面的代码:

首先在一个新的命名空间My.StringToInt32添加一个新的静态MystringMystring中再添加一个静态的方法ToInt32(),需要这个方法的第一个参数必须是(this 类型 变量)的形式。完整的代码是:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace My.StringToInt32

{

    //这里的类是静态的

    static class Mystring

    {

        //这里的方法也是静态的

        public static int ToInt32(this string s)

        {

            return Int32.Parse(s);

        }

    }

}

然后在原先的代码基础上做修改第一必须使用using把包含ToInt32()方法的命名空间引入,然后在string类型的变量上就可以使用“.”运算符来使用ToInt32()方法了。完整的代码是

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

//引入新方法的命名空间

using My.StringToInt32;

namespace StringToInt32

{

    class Program

    {

        static void Main(string[] args)

        {

            string str_num = "123";

            int num_str = Int32.Parse(str_num);

            Console.WriteLine((num_str * 2).ToString());

            //使用新添加的方法ToInt32()

            string newstr = "123";

            //像使用实例的方法一样使用刚才定义的方法

            int newnum = newstr.ToInt32();

            Console.WriteLine((newnum * 2).ToString());

            Console.ReadKey();

        }

    }

}

好了一个小的方法扩展实现了,在这里例子中充分展现了扩展方法的如何产生、如何使用的问题。略微总结一下。扩展方法是一种特殊的静态方法,但可以像被扩展类型上的实例方法一样进行调用。对于用 C# 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的。它们的第一个参数指定该方法将要作用于哪个类型,并且该参数以 this 修饰符为前缀。仅当您使用 using 指令将命名空间显式导入到源代码中之后,扩展方法才位于范围中。于是你就可以使用实例方法语法调用该扩展方法。下面看一下扩展方法的实现机制。同样使用ILDASM工具反编译刚才的代码。如图9-5所示。

1 

9-5

9-5说明程序实际调用的还是我们自己写的那个静态的方法ToInt32(),而并没有把扩展方法加入到原来的类中。实际程序的运行流程应该是:当程序运行到newstr.ToInt32()的时候,首先在string类型本身去寻找,结果发现string类型没有这个方法,于是就会按照引入的命名空间去寻找,在空间My.StringToInt32的静态类Mystring中找到,而且这个方法是静态的,然后把newstr本身传入。程序这样执行的流程也就解释了为什么扩展方法的包含类必须是静态的,而且方法也必须是静态的原因。也同时解释了为什么方法的第一个参数必须使用(this 类型 变量)格式。在使用扩展方法的程序代码中必须引入扩展方法的命名空间也是这个原因。

如果您确实为给定类型实现了扩展方法,根据上面程序执行的流程,可以看出以下两点规律:

1)如果扩展方法与该类型中定义的方法具有相同的签名,则扩展方法永远不会被调用。

2)扩展方法放在命名空间级别的范围中。例如,如果您在一个名为 Extensions 的命名空间中具有多个包含扩展方法的静态类,则这些扩展方法将全部由 using Extensions; 指令放入范围中。

另外如果在一个命名空间中找到两个都能使用的扩展方法,那么程序将会调用与调用的对象或者类型最精确匹配的那个扩展方法。例子SameExMethods就描述了这种情况,类Object没有Display方法,可以使用扩展方法的办法对它进行扩展,这样每个Object的子类都会有这个方法。在Display方法中输出这个类型的名字。同时也调用一个String类的对象,同样也可以使用Display方法打印出自己的类型的名字。

namespace My.SameExMethods

{

    static class MyExtension

    {

        public static void Display(this Object obj)

        {

            Console.WriteLine(obj.GetType().Name.ToString());

        }

    }

}

调用扩展方法的代码

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using My.SameExMethods;

 

namespace SameExMethods

{

    class Program

    {

        static void Main(string[] args)

        {

            Object TestObj = new Object();

            TestObj.Display();

            String str = "字符串";

            str.Display();

            Console.ReadKey();

        }

    }

}

程序的运行结果

2

 

如果这时在添加一个针对String类的Display扩展方法,那么对str.Display();的调用将会执行新添加的针对String类的Display扩展方法。新添加的扩展方法如下:

namespace My.SameExMethods

{

    static class MyExtension

    {

        public static void Display(this Object obj)

        {

            Console.WriteLine(obj.GetType().Name.ToString());

        }

        public static void Display(this String str)

        {

            Console.WriteLine(str);

        }

    }

}

调用扩展方法的代码不变,运行的结果:

 3

可以看到程序在寻找扩展方法的时候,会优先考虑扩展方法的第一个参数类型与本身类型最匹配的那个扩展方法。如果类型本身已经有了这个方法,那么扩展方法永远都不会本执行。于是你扩展了某个类型,那么如果这个类型由于升级等原因,具有了与你扩展方法相同名称的成员方法了,那么你的扩展方法将失效,也就意味着你的程序可能会出现意想不到的情况。

另外还有一种情况是扩展方法的嵌套,比如例子NestExMethod

namespace NestExMethod

{

    static class MyExtensionMethods

    {

        public static int Test01(this int i)

        {

            return i * 3;

        }

        public static int Test02(this int i)

        {

            return i + 5;

        }

    }

}

调用扩展方法的代码

namespace NestExMethod

{

    class Program

    {

        static void Main(string[] args)

        {

            int mm = 7;

            Console.WriteLine(mm.Test01().Test02());

            Console.WriteLine("***************");

            Console.WriteLine(mm.Test02().Test01());

            Console.WriteLine("***************");

            Console.WriteLine(MyExtensionMethods.Test02(MyExtensionMethods.Test01(mm)));

            Console.ReadLine();

        }

    }

}

程序的运行结果

4 

mm.Test01().Test02()语句为例,开始mm=7, mm.Test01()将会返回21,而21又是一个整型数据,于是21继续调用Test02(),返回结果21+5=26。这个扩展方法调用的结果又继续调用了另外的一个扩展方法。这种现象就是扩展方法的嵌套。