C#3.0新增功能04 扩展方法
扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。 对于用 C#、F# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法没有明显区别。
介绍如何为任意 .NET 类型实现自定义扩展方法。 客户端代码可以通过以下方法使用扩展方法,添加包含这些扩展方法的 DLL 的引用,以及添加 using 指令,该指令指定在其中定义扩展方法的命名空间。
定义和调用扩展方法
-
定义包含扩展方法的静态类。
此类必须对客户端代码可见。 有关可访问性规则的详细信息,请参阅访问修饰符。
-
将扩展方法实现为静态方法,并且使其可见性至少与所在类的可见性相同。
-
此方法的第一个参数指定方法所操作的类型;此参数前面必须加上 this 修饰符。
-
在调用代码中,添加
using
指令,用于指定包含扩展方法类的命名空间。 -
和调用类型的实例方法那样调用这些方法。
请注意,第一个参数并不是由调用代码指定,因为它表示要在其上应用运算符的类型,并且编译器已经知道对象的类型。
示例
以下示例实现 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 }
扩展方法不存在特定的安全漏洞。 始终不会将扩展方法用于模拟类型的现有方法,因为为了支持类型本身定义的实例或静态方法,已解决所有名称冲突。 扩展方法无法访问扩展类中的任何隐私数据。
在代码中,可以使用实例方法语法调用该扩展方法。 但是,编译器生成的中间语言 (IL) 会将代码转换为对静态方法的调用。 因此,并未真正违反封装原则。 实际上,扩展方法无法访问它们所扩展的类型中的私有变量。
通常,你更多时候是调用扩展方法而不是实现你自己的扩展方法。 由于扩展方法是使用实例方法语法调用的,因此不需要任何特殊知识即可从客户端代码中使用它们。 若要为特定类型启用扩展方法,只需为在其中定义这些方法的命名空间添加 using
指令。 例如,若要使用标准查询运算符,请将此 using
指令添加到代码中:
using System.Linq;
(你可能还必须添加对 System.Core.dll 的引用。)你将注意到,标准查询运算符现在作为可供大多数 IEnumerable<T> 类型使用的附加方法显示在 IntelliSense 中。
可以使用扩展方法来扩展类或接口,但不能重写扩展方法。 与接口或类方法具有相同名称和签名的扩展方法永远不会被调用。 编译时,扩展方法的优先级总是比类型本身中定义的实例方法低。 换句话说,如果某个类型具有一个名为 Process(int i)
的方法,而你有一个具有相同签名的扩展方法,则编译器总是绑定到该实例方法。 当编译器遇到方法调用时,它首先在该类型的实例方法中寻找匹配的方法。 如果未找到任何匹配方法,编译器将搜索为该类型定义的任何扩展方法,并且绑定到它找到的第一个扩展方法。 下面的示例演示编译器如何确定要绑定到哪个扩展方法或实例方法。
示例
下面的示例演示 C# 编译器在确定是将方法调用绑定到类型上的实例方法还是绑定到扩展方法时所遵循的规则。 静态类 Extensions
包含为任何实现了 IMyInterface
的类型定义的扩展方法。 类 A
、B
和 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 准则。有关详细信息,请参阅程序集版本控制。
其他技术请参阅
- C# 编程指南
- 并行编程示例(这些示例包括许多示例扩展方法)
- Lambda 表达式
- 标准查询运算符概述
- Conversion rules for Instance parameters and their impact(实例参数及其影响的转换规则)
- Extension methods Interoperability between languages(语言间扩展方法的互操作性)
- Extension methods and Curried Delegates(扩展方法和扩充委托)
- Extension method Binding and Error reporting(扩展方法绑定和错误报告)
成在管理,败在经验;嬴在选择,输在不学! 贵在坚持!
个人作品
BIMFace.SDK.NET
开源地址:https://gitee.com/NAlps/BIMFace.SDK
系列博客:https://www.cnblogs.com/SavionZhang/p/11424431.html
系列视频:https://www.cnblogs.com/SavionZhang/p/14258393.html
技术栈
1、Visual Studio、.NET Core/.NET、MVC、Web API、RESTful API、gRPC、SignalR、Java、Python
2、jQuery、Vue.js、Bootstrap、ElementUI
3、数据库:分库分表、读写分离、SQLServer、MySQL、PostgreSQL、Redis、MongoDB、ElasticSearch、达梦DM
4、架构:DDD、ABP、SpringBoot、jFinal
5、环境:跨平台、Windows、Linux、Nginx
6、移动App:Android、IOS、HarmonyOS、微信小程序、钉钉、uni-app、MAUI
分布式、高并发、云原生、微服务、Docker、CI/CD、DevOps、K8S;Dapr、RabbitMQ、Kafka、RPC、Elasticsearch。
欢迎关注作者头条号 张传宁IT讲堂,获取更多IT文章、视频等优质内容。
出处:www.cnblogs.com/SavionZhang
作者:张传宁 技术顾问、培训讲师、微软MCP、系统架构设计师、系统集成项目管理工程师、科技部创新工程师。
专注于企业级通用开发平台、工作流引擎、自动化项目(代码)生成器、SOA 、DDD、 云原生(Docker、微服务、DevOps、CI/CD);PDF、CAD、BIM 审图等研究与应用。
多次参与电子政务、图书教育、生产制造等企业级大型项目研发与管理工作。
熟悉中小企业软件开发过程:可行调研、需求分析、架构设计、编码测试、实施部署、项目管理。通过技术与管理帮助中小企业实现互联网转型升级全流程解决方案。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如有问题,可以通过邮件905442693@qq.com联系。共同交流、互相学习。
如果您觉得文章对您有帮助,请点击文章右下角【推荐】。您的鼓励是作者持续创作的最大动力!