首页 > 编程语言 > 深入理解C#中的扩展方法
2020
10-01

深入理解C#中的扩展方法

扩展方法(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(参数为空)异常。

编程技巧