专栏首页.NET技术与企业级解决方案C#3.0新增功能04 扩展方法

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 的类型定义的扩展方法。 类 ABC 都实现了该接口。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • C#3.0新增功能05 分部方法

      分部类或结构可以包含分部方法。 类的一个部分包含方法的签名。 可以在同一部分或另一个部分中定义可选实现。 如果未提供该实现,则会在编译时删除方法以及对方...

    张传宁老师
  • C#4.0新增功能03 泛型中的协变和逆变

    协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。泛...

    张传宁老师
  • C#4.0新增功能02 命名实参和可选实参

      C# 4 介绍命名实参和可选实参。 通过命名实参,你可以为特定形参指定实参,方法是将实参与该形参的名称关联,而不是与形参在形参列表中的位置关联。 通过可选参...

    张传宁老师
  • 扩展方法必须在非泛型静态类中定义

    扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一...

    yaphetsfang
  • 构造方法、方法的重载

    如果两个方法只是方法的返回值(返回值类型 )不一样,其他都一样,这构不成方法的重载,因为调用方法时无法确定调用的哪个方法,所以编译时会报错!

    爱学习的孙小白
  • 图数据表征学习,绝不止图神经网络一种方法

    近年来,图神经网络掀起了将深度学习方法应用于图数据分析的浪潮。不过其作为一门古老的认识世界的方法论,人们对于图数据表征技术的研究从很早以前就开始了。

    AI科技评论
  • java里getter和setter的作用和区别是什么?

    java是典型的面向对象的编程语言,面向对象三个特性,继承性,多态性,封装性,主要和封装性考虑,类里面的变量不想设置成公共的类型,但是还要给外部使用在这种实用场...

    程序员互动联盟
  • C# 永远不会返回的方法真的不会返回

    一般情况下,如果一个方法声明了返回值,但是实际上在编写代码的时候没有返回,那么这个时候会出现编译错误。

    walterlv
  • Java框架学习springAOP—顾问把通知封装起来

    好的各位小伙伴 又到了分享知识的时间 准备好了吗? 今天的分享的顾问 顾问(Advisor) 顾问是将通知进行了包装,根据通知的不同类型,在不同的时间点,将切面...

    企鹅号小编
  • 科普|机器学习中决策树的原理与算法

    AI科技评论按:本文作者栗向滨,中科院自动化所复杂系统国家重点实验室研究生毕业,机器学习与计算机视觉方向算法工程师。雷锋网首发文章。 我们知道,在机器学习中有两...

    AI科技评论

扫码关注云+社区

领取腾讯云代金券