什么是扩展方法
扩展方法,首先是一种方法,它可以用来扩展已定义类型中的方法成员
在扩展方法诞生之前,如果想位一个已有类型自定义含有特殊逻辑的新方法时,你必须重新定义一个类型来继承已有类型,以这种方法来添加方法,如果基类有抽象方法,则还要重新实现这些抽象方法
这样,为了扩展一个方法,我们需要承受更多因继承而产的开销。如果继承来扩展现有类型总有些大材小用的感觉。并且值类型或密封累等也不能被继承,不能由此而获得扩展
于是,C# 3.0 提出了扩展方法,用它来为现有的类型添加方法,从未解决了使用继承进行扩展所带来的弊端
扩展方法的使用
定义扩展方法
代码语言:javascript复制public static class IEnumerableExtensions
{
public static bool IsEmpty<T>(this IEnumerable<T> ts)
{
return ts == null || ts.Count() == 0;
}
}
从以上代码中,IsEmpty 方法就是一个扩展方法,它的功能就是判断集合是否为空
并不是所有方法都可以作为扩展方法,如何分辨代码中定义的是扩展方法还是普通方法呢?我们需要考察它是否符合下列扩展方法的定义规则
- 扩展方法必须在一个非嵌套、非泛型的静态类中定义
- 至少要有一个参数
- 第一个参数必须加速this关键字作为前缀(第一个参数类型也称为扩展类型,即指方法对这个类型进行扩展)
- 第一个参数不能使用任何其他的修饰符(如不能使用 ref、out 等修饰符)
- 第一个参数的类型不能是指针类型
这些规则都是硬性规定,无论方法违反了哪一条,编译器都会报错,或认为它不是一个扩展方法
调用扩展方法
成功定义了一个扩展方法后,接下来就是该去调用它
代码语言:javascript复制class Program
{
static void Main(string[] args)
{
List<string> list = new List<string>();
Console.WriteLine(list.IsEmpty()); // true
}
}
从以上代码中,list.IsEmpty()
调用了我们定义的扩展方法
编译器如何发现扩展方法
编译器会检查所有导入的命名空间和当前命名空间中的扩展方法,并将变量类型匹配到扩展类型,这里存在一个隐式转换的扩展方法
从编译器发现扩展方法的过程来看,方法调用的优先级顺序为:
1、类型的实例方法
2、当前命名空间下的扩展方法
3、导入命名空间的扩展方法
空引用也可调用扩展方法
在 C# 中,在空引用(即 null)上调用实例方法会引发 NullReferenceException 异常,但在空引用上却可以调用扩展方法
代码示例
代码语言:javascript复制using System;
using System.Linq.Expressions;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
string str = null;
Console.WriteLine(str.IsNull());
}
}
static class NullExtension
{
public static bool IsNull(this object obj)
{
return obj == null;
}
}
}
以上代码在空引用上去调用扩展方法确实没有出现 NullReferenceException 异常
这段代码中扩展的定义是不规范的。代码扩展了 object 类型,所有继承于 object 的类型都将具有该扩展方法,这就对其他子类型产生了“污染”。之所以叫它污染,是因为我们定义的扩展方法本来只想扩展某个子类,却意外地造成了原本不想造成的后果。更好的实现方式如下:
代码语言:javascript复制public static bool IsNull(this string str)
{
return str == null;
}
所以当我们为一个类型定义扩展方法时,尽量扩展具体的类型,而不要扩展其基类
在空引用上调用扩展方法没有报错,是因为对于编译器而言,这个过程只是把空引用 “str” 当成参数传入静态方法而已