C#3.0新增功能04 扩展方法

  扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。 对于用 C#、F# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法没有明显区别。

实现和调用自定义扩展方法

  介绍如何为任意 .NET 类型实现自定义扩展方法。 客户端代码可以通过以下方法使用扩展方法,添加包含这些扩展方法的 DLL 的引用,以及添加 using 指令,该指令指定在其中定义扩展方法的命名空间。

定义和调用扩展方法

  1. 定义包含扩展方法的静态

    此类必须对客户端代码可见。 有关可访问性规则的详细信息,请参阅访问修饰符

  2. 将扩展方法实现为静态方法,并且使其可见性至少与所在类的可见性相同。

  3. 此方法的第一个参数指定方法所操作的类型;此参数前面必须加上 this 修饰符。

  4. 在调用代码中,添加 using 指令,用于指定包含扩展方法类的命名空间

  5. 和调用类型的实例方法那样调用这些方法。

    请注意,第一个参数并不是由调用代码指定,因为它表示要在其上应用运算符的类型,并且编译器已经知道对象的类型。

示例

以下示例实现 CustomExtensions.StringExtension 类中名为 WordCount 的扩展方法。 此方法对 String 类进行操作,该类指定为第一个方法参数。 将 CustomExtensions 命名空间导入应用程序命名空间,并在 Main 方法内部调用此方法。

 1 using System.Linq;
 2 using System.Text;
 3 using System;
 4 
 5 namespace CustomExtensions
 6 {
 7     // 扩展方法必须定义在静态类的内部
 8     public static class StringExtension
 9     {
10         // 这是一个扩展方法:第一个参数必须使用 this 关键字修饰,指定为其定义方法的类型
11         public static int WordCount(this String str)
12         {
13             return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
14         }
15     }
16 }
17 
18 namespace Extension_Methods_Simple
19 {
20     // 导入扩展方法的命名空间
21     using CustomExtensions;
22     class Program
23     {
24         static void Main(string[] args)
25         {
26             string s = "The quick brown fox jumped over the lazy dog.";
27             int i = s.WordCount(); // 调用该方法,就像它是该类型上的实例方法一样。注意,第一个参数不是由调用代码指定的
28             System.Console.WriteLine("Word count of s is {0}", i);
29         }
30     }
31 }
.NET Framework 安全性

  扩展方法不存在特定的安全漏洞。 始终不会将扩展方法用于模拟类型的现有方法,因为为了支持类型本身定义的实例或静态方法,已解决所有名称冲突。 扩展方法无法访问扩展类中的任何隐私数据。

  在代码中,可以使用实例方法语法调用该扩展方法。 但是,编译器生成的中间语言 (IL) 会将代码转换为对静态方法的调用。 因此,并未真正违反封装原则。 实际上,扩展方法无法访问它们所扩展的类型中的私有变量。

通常,你更多时候是调用扩展方法而不是实现你自己的扩展方法。 由于扩展方法是使用实例方法语法调用的,因此不需要任何特殊知识即可从客户端代码中使用它们。 若要为特定类型启用扩展方法,只需为在其中定义这些方法的命名空间添加 using 指令。 例如,若要使用标准查询运算符,请将此 using 指令添加到代码中:

using System.Linq;

(你可能还必须添加对 System.Core.dll 的引用。)你将注意到,标准查询运算符现在作为可供大多数 IEnumerable<T> 类型使用的附加方法显示在 IntelliSense 中。

在编译时绑定扩展方法

可以使用扩展方法来扩展类或接口,但不能重写扩展方法。 与接口或类方法具有相同名称和签名的扩展方法永远不会被调用。 编译时,扩展方法的优先级总是比类型本身中定义的实例方法低。 换句话说,如果某个类型具有一个名为 Process(int i) 的方法,而你有一个具有相同签名的扩展方法,则编译器总是绑定到该实例方法。 当编译器遇到方法调用时,它首先在该类型的实例方法中寻找匹配的方法。 如果未找到任何匹配方法,编译器将搜索为该类型定义的任何扩展方法,并且绑定到它找到的第一个扩展方法。 下面的示例演示编译器如何确定要绑定到哪个扩展方法或实例方法。

示例

下面的示例演示 C# 编译器在确定是将方法调用绑定到类型上的实例方法还是绑定到扩展方法时所遵循的规则。 静态类 Extensions 包含为任何实现了 IMyInterface 的类型定义的扩展方法。 类 AB 和 C 都实现了该接口。

MethodB 扩展方法永远不会被调用,因为它的名称和签名与这些类已经实现的方法完全匹配。

如果编译器找不到具有匹配签名的实例方法,它会绑定到匹配的扩展方法(如果存在这样的方法)。

  1 namespace DefineIMyInterface
  2 {
  3     using System;
  4 
  5     public interface IMyInterface
  6     {
  7         // 实现 IMyInterface 接口的任何类都必须定义与以下签名匹配的方法
  8         void MethodB();
  9     }
 10 }
 11 
 12 
 13 // 定义三个实现 IMyInterface 的类,然后使用它们来测试扩展方法。
 14 namespace ExtensionMethodsDemo1
 15 {
 16     using System;
 17     using Extensions;
 18     using DefineIMyInterface;
 19 
 20     class A : IMyInterface
 21     {
 22         public void MethodB() { Console.WriteLine("A.MethodB()"); }
 23     }
 24 
 25     class B : IMyInterface
 26     {
 27         public void MethodB() { Console.WriteLine("B.MethodB()"); }
 28         public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
 29     }
 30 
 31     class C : IMyInterface
 32     {
 33         public void MethodB() { Console.WriteLine("C.MethodB()"); }
 34         public void MethodA(object obj)
 35         {
 36             Console.WriteLine("C.MethodA(object obj)");
 37         }
 38     }
 39 
 40     // 定义 IMyInterface 的扩展方法
 41     namespace Extensions
 42 {
 43     using System;
 44     using DefineIMyInterface;
 45 
 46     // 实现 IMyInterface 的任何类的实例都可以访问以下扩展方法
 47     public static class Extension
 48     {
 49         public static void MethodA(this IMyInterface myInterface, int i)
 50         {
 51             Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
 52         }
 53 
 54         public static void MethodA(this IMyInterface myInterface, string s)
 55         {
 56             Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
 57         }
 58 
 59         // ExtensionMethodsDemo1 类中永远不会调用此方法,
 60         // 因为三个类A、B和C中的每一个都实现了名为methodB的方法,该方法具有匹配的签名。
 61         public static void MethodB(this IMyInterface myInterface)
 62         {
 63             Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
 64         }
 65     }
 66 }
 67 
 68     class ExtMethodDemo
 69     {
 70         static void Main(string[] args)
 71         {
 72             A a = new A();
 73             B b = new B();
 74             C c = new C();
 75 
 76 
 77             // A 不包含 MethodA,因此对 MethodA 的每个调用都解析为具有匹配签名的扩展方法
 78             a.MethodA(1);           // Extension.MethodA(IMyInterface, int)
 79             a.MethodA("hello");     // Extension.MethodA(IMyInterface, string)
 80 
 81             // A 有一个方法与对 MethodB 的以下调用的签名匹配
 82             a.MethodB();              // A.MethodB()
 83 
 84             // B 具有与以下方法调用的签名匹配的方法
 85             b.MethodA(1);           // B.MethodA(int)
 86             b.MethodB();             // B.MethodB()
 87 
 88             // B 没有用于以下调用的匹配方法,但是类扩展名有
 89             b.MethodA("hello");     // Extension.MethodA(IMyInterface, string)
 90              
 91             // C 包含一个匹配以下每个方法调用的实例方法
 92             c.MethodA(1);           // C.MethodA(object)
 93             c.MethodA("hello");     // C.MethodA(object)
 94             c.MethodB();                // C.MethodB()
 95         }
 96     }
 97 }
 98 
 99 /* 输出:
100     Extension.MethodA(this IMyInterface myInterface, int i)
101     Extension.MethodA(this IMyInterface myInterface, string s)
102     A.MethodB()
103     B.MethodA(int i)
104     B.MethodB()
105     Extension.MethodA(this IMyInterface myInterface, string s)
106     C.MethodA(object obj)
107     C.MethodA(object obj)
108     C.MethodB()
109  */
 
通用准则

通常,建议你只在不得已的情况下才实现扩展方法,并谨慎地实现。 只要有可能,必须扩展现有类型的客户端代码都应该通过创建从现有类型派生的新类型来达到这一目的。 有关详细信息,请参阅继承

在使用扩展方法来扩展你无法更改其源代码的类型时,你需要承受该类型实现中的更改会导致扩展方法失效的风险。

如果确实为给定类型实现了扩展方法,请记住以下几点:

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

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

针对已实现的类库,不应为了避免程序集的版本号递增而使用扩展方法。 如果要向你拥有源代码的库中添加重要功能,应遵循适用于程序集版本控制的标准 .NET Framework 准则。有关详细信息,请参阅程序集版本控制

其他技术请参阅

 

posted @ 2019-07-11 20:27  张传宁  阅读(432)  评论(0编辑  收藏  举报
页脚 HTML 代码