扩展方法和成员方法:为什么编译器(内部)对每个方法的实现不同?
考虑一下这段代码:
A a = null;
a.f(); //Will it throw NullReferenceException?
上面的代码会抛出 NullReferenceException 吗?
答案是:这取决于 f()
是什么。
- 如果是成员方法,那么是的,它会抛出异常。
- 如果它是扩展方法,则不,它不会抛出任何扩展。
这种差异引出了一个问题:C# 编译器如何实现和查看每种类型的方法?另外,为什么成员方法必须抛出异常即使它不访问任何成员数据?看来C#编译器提前假设成员方法将访问成员数据,因此如果对象为null,它会抛出异常,因为使用null对象无法访问成员数据。但是,在扩展方法的情况下,它会推迟此决定,直到它实际上尝试使用空引用访问成员数据,
我的理解在多大程度上是正确的?如果是这样,为什么会有这种差异?
是的,我知道如果f()
是一个扩展方法,那么af()
就相当于写AExt.f(a)
,所以后者不应该抛出异常,直到使用 a
访问成员。但我的重点主要是编译器实现(它甚至可以以相同的方式实现成员方法)。
Consider this code:
A a = null;
a.f(); //Will it throw NullReferenceException?
Will the above throw NullReferenceException
?
The answer is : it depends on what f()
is.
- If it's a member method, then yes, it will throw exception.
- If it's an extension method, then no, it will not throw any extension.
This difference leads to a question: how each type of method is implemeneted and viewed by C# compilers? Also, why member method must throw exception even if it doesn't access any member data? It seems that C# compiler makes an assumption in advance that member method will access member data, and so it throws exception if the object is null, as using null object member data cannot be accessed. However, in case of extension method, it postpones this decision till it actually attempts to access member data using null reference, only then it throws exception.
How far my understanding is correct? And if that is so, why this difference?
Yes, I know that if f()
is an extension method, then a.f()
is equivalent to writing AExt.f(a)
, so the latter shouldn't throw exception until a
is used to access member. But my focus is mostly on the compiler implementations (which can implement even member methods in the same way).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
是的,这就是这段代码的行为方式(如果扩展方法不检查
null
并自行引发异常)。您是对的,如果该方法不访问该类的任何实例字段(直接或间接),则在null
上调用非虚拟实例方法可以工作。但是语言设计人员认为这会令人困惑,因此他们通过使用
callvirt
IL 指令来确保该对象不为null
。使用扩展方法,这并不那么令人困惑(没有人期望
this
为null
,但声明的参数this IEnumerablesource
应该是检查)。并且您可以像普通静态方法一样调用扩展方法,因此无论如何它都应该检查null
。此外,调用该函数的两种方式的行为可能应该完全相同。Yes, this is how this code behaves (if the extension method doesn't check for
null
and throws an exception by itself). You're right that calling non-virtual instance method onnull
could work, if that method doesn't access any instance fields of the class (directly or indirectly).But the language designers felt this would be confusing, so they make sure that the object is not
null
by using thecallvirt
IL instruction.With extension methods, this is not as confusing (nobody expects
this
to benull
, but an argument declaredthis IEnumerable<TSource> source
should be checked). And you can call extension method as normal static method, so it should check fornull
anyway. Also, both ways of calling the function should probably behave exactly the same.当对象为 null 时,实例方法必须抛出 NullReferenceException,因为它们本质上保证在执行该方法时该对象可用。将实例方法视为对象实例执行的操作,当对象本身不存在时,甚至无法调用该操作。扩展方法只是语法糖,编译器会立即将它们转换为您上面提到的
AExt.f(a)
。这只是一种便利,并且可以作为一种视觉效果来帮助更好地阅读代码。对于无论实例是否可用都希望调用方法的情况,该语言提供静态方法作为功能。正如您所知,扩展方法是静态方法。
The instance methods must throw the
NullReferenceException
when the object is null, because they inherently guarantee that the object is available when that method is executed. Think of the instance methods as an action that the object instance performs and when the object itself is not existent, then the action could not even be invoked. The extension methods are just syntactic sugar and the compiler immediately translates them as you mentioned aboveAExt.f(a)
. This is just a convenience and serves as an eye candy to help read the code better.For the cases where you would want to call a method regardless of whether the instance is available or not, the language provides static methods as the feature. As you can tell, the extension methods are static methods.