扩展方法

  扩展方法可以扩展类型的功能,但不必修改类型本身。甚至可以使用扩展方法扩展不能修改的类型,包括在.NET Framework中定义的类型。例如,使用扩展方法甚至可以给System.String等基本类型添加功能。

  为了扩展类型的功能,需要提供可以通过该类型的实例调用的方法。为此创建的方法称为扩展方法(extension method),它可以带任意数量的参数,返回任意类型(包括void)。要创建和使用扩展方法,必须:

(1)创建一个非泛型静态类。
(2)使用扩展方法的语法,为所创建的类添加扩展方法,作为静态方法(稍后介绍)。
(3)确保使用扩展方法的代码用using语句导入了包含扩展方法类的名称空间。
(4)通过扩展类型的一个实例调用扩展方法,与调用扩展类型的其他方法一样。

  C#编译器在第(3)步和第(4)步之间完成了它的使命。IDE会立即发现我们创建了一个扩展方法,并显示在IntelliSense中,如图14-11所示。

  在图14-11中,可通过string对象(这里仅是一个字面量字符串)使用扩展方法MyMarvelousExtensionMethod()。这个方法用一个略微不同的方法图标来表示,该图标包含一个向下箭头,这个方法不带其他参数,返回一个字符串。

  为了定义扩展方法,应采用与其他方法相同的方式定义一个方法,但该方法必须满足扩展方法的语法要求。这些要求如下:

    • 方法必须是静态的。
    • 方法必须包含一个参数,表示调用扩展方法的类型实例(该参数在这里称为实例参数)。
    • 实例参数必须是为方法定义的第一个参数(与其他方法一样,之后可以定义任意多个参数)。
    • 除了this关键字外,实例参数不能有其他修饰符。

  扩展方法的语法如下:

    public static class ExtensionClass
    {
        public static <ReturnType> <ExtensionMethodName>(
            this <TypeToExtend> instance, <OtherParameters>)
        {
            ...
        }
    }

  导入了包含静态类(其中包括此方法)的名称空间后(也就是使扩展方法变得可用),就可以编写如下代码:

    <TypeToExtend> myVar;
    // myVar is initialized by code not shown here.
    myVar.<ExtensionMethonName>();

  还可以在扩展方法中包含需要的其他参数,并使用其返回类型。   这个调用实际上与以下调用相同,但语法更加简单:

    <TypeToExtend> myVar;
    // myVar is initialized by code not shown here.
    ExtensionClass.<ExtensionMethodName>(myVar);

  另一个优点是,导入后,就可以通过IntelliSense查看扩展方法,这样能容易地找到需要的功能。扩展方法可能分布在多个扩展类中,甚至分布在多个库中,但它们都会显示在扩展类型的成员列表中。

  定义了可以用于某个类型的扩展方法后,还可以把它用于派生于这个类型的子类型。在本章前面的一个示例中,如果为Animal类定义了一个扩展方法,就可以诸如Cow的对象上调用它。

  还可以定义在特定接口上执行的扩展方法,接着就可以为实现了该接口的任意类型使用该扩展方法。

  扩展方法为在应用程序中重用实用代码库提供了一种方式。它们还可以广泛用于本书后面介绍的LINQ中。为更好地理解它,下面来分析一个示例。

    namespace ExtensionLib
    {
        public static class WordProcessor
        {
            public static List<string> GetWords(
                this string sentence,
                bool capitalizeWords = false,
                bool reverseOrder = false,
                bool reverseWords = false)
            {
                ...
            }
            ...
            public static string ToStringReversed(this object inputObject)
            {
                return ReverseWord(inputObject.ToString());
            }

            public static string AsSentence(this List<string> words)
            {
                StringBuilder sb = new StringBuilder();
                for(int wordIndex = 0; wordIndex < words.Count; wordIndex++)
                {
                    sb.Append(words[wordIndex]);
                    if(wordIndex != words.Count - 1)
                    {
                        sb.Append(' ');
                    }
                }
                return sb.ToString();
            }
        }
    }

  修改Program.cs中的代码,如下所示:

    using ExtensionLib;

    namespace Ch14Ex05
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Enter a string to convert:");
                string sourceString = Console.ReadLine();
                Console.WriteLine("String with title casing: {0}", sourceString.GetWords(capitalizeWords : true).AsSentence());
                Console.WriteLine("String backwards: {0}",
                    sourceString.GetWords(reverseOrder: true,
                        reverseWords: true).AsSentence());
                Console.WriteLine("String length backwards: {0}", sourceString.Length.ToStringReversed());
                Console.ReadKey();
            }
        }
    }

  示例的说明

  这个示例创建了一个类库,其中包含在一个简单客户应用程序中使用的实用扩展方法。这个类库有一个静态类WordProcessor的扩展版本,WordProcessor来自上一个包含扩展方法的“试一试”示例,我们把包含这个类的名称空间ExtensionLib导入客户应用程序,以便使用这些扩展方法。

  我们创建了3个扩展方法,如表14-1所示。

表14-1扩展方法

方法 描述
GetWords() 操作字符串的灵活方法,如上个示例所示。在这个示例中,这个方法改为扩展方法,返回一个List<string>
ToStringReversed() 对于在对象上调用ToString()所返回的字符串,使用ReverseWord()使其中的字母逆序,并返回一个字符串
AsSentence() "铺平"一个List<string>对象,返回一个字符串,该字符串由它包含的单词组成

  客户代码一次调用这些方法,以各种方式修改输入的字符产。前面定义的GetWords()方法返回一个List<string>,所以使用AsSentence()把它的输出结果铺平为一个字符串,以便于使用。

  扩展方法ToStringReversed()是一个比较一般的扩展方法,它不需要string类型的实例参数,而是有一个object类型的实例参数。这表示这个扩展方法可以在任意对象上调用,在IntelliSense中会显示在所使用的每个对象上。在这个扩展方法中没有做太多工作,因为不能对要使用的对象做太多假定。可使用is运算符或尝试类型转换,来确定实例参数的类型,并据此执行相应的操作,也可以执行这个例子的操作,使用所有对象都支持的基本功能---ToString()方法:

    public static string ToStringReversed(this object inputObject)
    {
        return ReverseWord(inputObject.ToString());
    }

  这个方法只是在其实例参数上调用了ToString()方法,用前面的ReverseWord()方法使实例参数中的字母逆序。在示例客户程序中,从int变量上调用了ToStringReversed()方法,得到该整数的一个字符串表示,其中的数字顺序被颠倒了。

  可在多个类型上使用的扩展方法非常有用。另外,还可以定义泛型扩展方法,它们可以把约束应用于类型,如第12章所述。

🔚