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

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

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

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

定义和调用扩展方法

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

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

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

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

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

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

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

示例

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

 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 准则。有关详细信息,请参阅程序集版本控制

其余技术请参阅

 

相关文章
相关标签/搜索