扩展方法

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

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

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

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

 14.6 扩展方法  - 图1

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

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

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

  扩展方法的语法如下:

  1. public static class ExtensionClass
  2. {
  3. public static <ReturnType> <ExtensionMethodName>(
  4. this <TypeToExtend> instance, <OtherParameters>)
  5. {
  6. ...
  7. }
  8. }

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

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

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

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

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

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

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

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

  1. namespace ExtensionLib
  2. {
  3. public static class WordProcessor
  4. {
  5. public static List<string> GetWords(
  6. this string sentence,
  7. bool capitalizeWords = false,
  8. bool reverseOrder = false,
  9. bool reverseWords = false)
  10. {
  11. ...
  12. }
  13. ...
  14. public static string ToStringReversed(this object inputObject)
  15. {
  16. return ReverseWord(inputObject.ToString());
  17. }
  18. public static string AsSentence(this List<string> words)
  19. {
  20. StringBuilder sb = new StringBuilder();
  21. for(int wordIndex = 0; wordIndex < words.Count; wordIndex++)
  22. {
  23. sb.Append(words[wordIndex]);
  24. if(wordIndex != words.Count - 1)
  25. {
  26. sb.Append(' ');
  27. }
  28. }
  29. return sb.ToString();
  30. }
  31. }
  32. }

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

  1. using ExtensionLib;
  2. namespace Ch14Ex05
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. Console.WriteLine("Enter a string to convert:");
  9. string sourceString = Console.ReadLine();
  10. Console.WriteLine("String with title casing: {0}", sourceString.GetWords(capitalizeWords : true).AsSentence());
  11. Console.WriteLine("String backwards: {0}",
  12. sourceString.GetWords(reverseOrder: true,
  13. reverseWords: true).AsSentence());
  14. Console.WriteLine("String length backwards: {0}", sourceString.Length.ToStringReversed());
  15. Console.ReadKey();
  16. }
  17. }
  18. }
  示例的说明  这个示例创建了一个类库,其中包含在一个简单客户应用程序中使用的实用扩展方法。这个类库有一个静态类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()方法:

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

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

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