扩展方法和成员方法:为什么编译器(内部)以不同方式实现每个方法?

时间:2011-09-03 19:36:06

标签: c# extension-methods nullreferenceexception member

考虑以下代码:

 A a = null;
 a.f(); //Will it throw NullReferenceException?

上面会抛出NullReferenceException吗?

答案是:这取决于f()是什么。

这种差异导致了一个问题:C#编译器如何实现和查看每种类型的方法?另外,为什么成员方法必须抛出异常,即使它不访问任何成员数据?似乎C#编译器提前假设 成员方法将访问成员数据,因此如果对象为null则抛出异常,因为无法访问使用null对象成员数据。但是,在扩展方法的情况下,它会推迟此决定,直到实际尝试使用空引用only then it throws exception来访问成员数据。

我的理解到底有多远?如果是这样,为什么会出现这种差异?

是的,我知道如果f()是一种扩展方法,那么a.f()相当于编写AExt.f(a),因此后者不应该抛出异常,直到a为用来访问会员。但我的重点主要放在编译器实现上(它可以以相同的方式实现成员方法)。

2 个答案:

答案 0 :(得分:2)

是的,这是此代码的行为方式(如果扩展方法不检查null并自行抛出异常)。你是正确的,如果该方法不能访问该类的任何实例字段(直接或间接),则null上的非虚拟实例方法可以正常工作。

但是the language designers felt this would be confusing,所以他们通过使用null IL指令确保对象不是callvirt

使用扩展方法,这并不会令人困惑(没有人希望thisnull,但应该检查声明this IEnumerable<TSource> source的参数。并且您可以将扩展方法称为常规静态方法,因此无论如何都应该检查null。此外,两种调用函数的方式应该完全相同。

答案 1 :(得分:1)

当对象为null时,实例方法必须抛出NullReferenceException,因为它们本身保证在执行该方法时对象可用。将实例方法视为对象实例执行的操作以及当对象本身不存在时,甚至无法调用该操作。扩展方法只是语法糖,编译器会立即翻译它们,如上所述AExt.f(a)。这只是一个便利性,可以帮助您更好地阅读代码。

对于您想要调用方法而不管实例是否可用的情况,该语言提供静态方法作为功能。如您所知,扩展方法是静态方法。