扩展方法(Extension Methods)是C#3.0时引入的新特性,相信很多人都听过并且也都用过,最常见的是在LINQ中的使用。
不仅如此,在开发中,我们也可以创建自己扩展方法,使用它来优化类的设计、简化代码。本文将简单地介绍扩展方法的概念、定义、使用场景以及要注意的点。
一、概念
扩展方法是一种特殊类型的静态方法。对于一个C#类型,如类(包括密封类)、值类型、接口等,扩展方法可以在不改变该类型源码的前提下,为它的实例提供新的成员。因此,若要为一个框架或第三方库的某个类型增加辅助功能,通过扩展方法就可以轻而易举地实现,这也是“扩展”的意义所在。
二、如何定义
创建扩展方法很简单,有以下几个步骤:
1、创建一个静态类;
2、在其中创建一个静态方法;
3、为这个静态方法添加至少一个参数,并在第一个参数前加上this关键字,这个关键字会告诉编译器当前方法是一个扩展方法。而这个方法将成为第一个参数所属类型的新成员。
以下一个典型的扩展方法,用于为枚举值提供一个可获取其DescriptionAttribute特性值的方法:
namespace TLA. Infrastructure. Extensions public static class EnumExtensions { public static string GetDescription(this Enum en) { Type type = en. GetType(); MemberInfo[] memInfo = type . GetMember(en. ToString()); if (memInfo != null && memInfo.Length > 0) { object[] attrs = memInfo[0] . GetCustomAttributes (typeof (DescriptionAttribute), false); if (attrs != null && attrs. Length > 0){ return ( (DescriptionAttribute )attrs [0]) . Description; } } return en. ToString(); } } }
注意:只有在引用扩展方法所在的静态类的命名空间后,才能使用它;否则,直接调用会编译失败。上例中,使用该扩展方法要引用TLA.Infrastructure.Extensions命名空间。
三、何时使用
从扩展方法的概念上,不难看出,它可以用在以下几种场合:
1、要为某个类型扩展功能,但没有其源码,比如某个框架或第三方库中的一个类;例如,想要获取一个列表中所有的奇数项,就可以为IList<T>接口增加一个扩展方法,这里的IList<T>接口本身是.NET框架中的接口。
public static class IListExtentions { public static IEnumerable<T> OddItems<T>(this IEnumerable<T> list) { if (list == nu1l) { throw new ArgumentNullExcept ion (nameof(list)) ; } for (int i = 0; i < list. Count(); i++) { if(i%2==0) { yield return list. ElementAt(i); } } } }
2、即使可以访问原有类型的源码,也可以使用扩展方法为它添加辅助功能;
public interface ILog { void Log(string message, LogLevel logLevel); } public static class ILogExtensions { /// <summary> ///记录调试信息 /// </summary> /// <remarks>扩 展方法,方便记录调试信息</ remarks> public static void LogDebug(this ILog logger, string message ) { if (true) // 判断日志配置中是否允许输入Debug类型的日志 { logger? .Log($" {message}", LogLevel.Debug); } } }
3、重用代码,使代码更简洁;由于扩展方法封装了一段完整的逻辑,所以,使用扩展方法就避免了复制粘贴代码的情况。上例中扩展方法的内容也符合这种使用场景。
四、注意事项
以下是定义与使扩展方法时的一些注意事项和最佳实践:
1、扩展方法本质上是为原有类型提供辅助功能,因此,在创建时,要确保它具有实际意义,且遵循单一职责原则;也即,不能过度使用扩展方法并且它能够完成一个具体、完整的功能;
2、扩展方法本身具有通用性,因此,它里面应避免特定的业务数据类型及其相关逻辑;
3、如果为接口增加扩展方法,扩展方法的命名空间可以与接口的一致;否则,应尽量避免与原类型写在同一命名空间下,这样会“污染”原类型。建议的做法是为扩展方法所在的类设定一个单独的命名空间,如:<Company>.<Product>.Extentions。不过,这样做也有缺点:在操作原有类型的实例时,如果不引用扩展方法所在的命名空间,那么,它就不容易被发现,而解决这个问题的办法是,尽量将扩展方法文档化,并告诉项目组的其他开发人员;
4、为接口增加扩展方法后,则所有实现此接口的类都会包含该扩展方法;
5、在扩展方法中,要对第一个参数进行非空检查,如果为空,应抛出ArgumentNullException(参数为空)异常。