问题根本不在于创建这样的方法。它与调用方法有关。如果您if(this == null)
在代码中进行测试,那是完全有效的。我想它可以被编译器优化掉,因为它“不可能”被击中,但幸运的是它不是。
但是,当您调用该方法时,它将通过 完成callvirt
,因此它不会直接调用该方法,而是会找到该方法的版本以调用特定实例,就像使用虚拟方法一样。由于空引用将失败,因此您完美的自空测试方法将在它被调用之前失败。
C# 故意这样做。根据 Eric Gunnerson的说法,这是因为他们认为让你这样做会有点奇怪。
我一直不明白为什么让以 C++ 为模型的 .NET 语言在 .NET 和同一家公司生产的 C++ 编译器中完全允许做一些事情,* 被认为有点奇怪。我一直认为不允许这样做有点奇怪。
您可以从调用该类的另一种语言(F# 或 IL)中添加一些内容,或者使用它Reflection.Emit
来生成执行此操作的委托,这样就可以正常工作。例如,以下代码将调用GetHashCode
定义在的版本object
(即,即使GetHashCode
被覆盖,this 也不会调用覆盖),这是可以安全调用空实例的方法的示例:
DynamicMethod dynM = new DynamicMethod(string.Empty, typeof(int), new Type[]{typeof(object)}, typeof(object));
ILGenerator ilGen = dynM.GetILGenerator(7);
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, typeof(object).GetMethod("GetHashCode"));
ilGen.Emit(OpCodes.Ret);
Func<object, int> RootHashCode = (Func<object, int>)dynM.CreateDelegate(typeof(Func<object, int>));
Console.WriteLine(RootHashCode(null));
这样做的一个好处是你可以坚持,RootHashCode
所以你只需要构建一次(比如在静态构造函数中),然后你就可以重复使用它。
这对于让其他代码通过空引用调用您的方法当然没有任何价值,因为您建议的扩展方法是您唯一的选择。
当然,同样值得注意的是,如果您使用的语言没有 C# 的这种怪癖,那么您应该提供一些替代方法来获取调用空引用的“默认”结果,因为 C# 人们可以'不明白。就像 C# 一样,人们应该避免公共名称之间仅区分大小写,因为某些语言无法处理这种情况。
编辑:您的问题IsAuthorized
被调用的完整示例,因为投票表明有些人不相信可以做到(!)
using System;
using System.Reflection.Emit;
using System.Security;
/*We need to either have User allow partially-trusted
callers, or we need to have Program be fully-trusted.
The former is the quicker to do, though the latter is
more likely to be what one would want for real*/
[assembly:AllowPartiallyTrustedCallers]
namespace AllowCallsOnNull
{
public class User
{
public bool IsAuthorized
{
get
{
//Perverse because someone writing in C# should be expected to be friendly to
//C#! This though doesn't apply to someone writing in another language who may
//not know C# has difficulties calling this.
//Still, don't do this:
if(this == null)
{
Console.Error.WriteLine("I don't exist!");
return false;
}
/*Real code to work out if the user is authorised
would go here. We're just going to return true
to demonstrate the point*/
Console.Error.WriteLine("I'm a real boy! I mean, user!");
return true;
}
}
}
class Program
{
public static void Main(string[] args)
{
//Set-up the helper that calls IsAuthorized on a
//User, that may be null.
DynamicMethod dynM = new DynamicMethod(string.Empty, typeof(bool), new Type[]{typeof(User)}, typeof(object));
ILGenerator ilGen = dynM.GetILGenerator(7);
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, typeof(User).GetProperty("IsAuthorized").GetGetMethod());
ilGen.Emit(OpCodes.Ret);
Func<User, bool> CheckAuthorized = (Func<User, bool>)dynM.CreateDelegate(typeof(Func<User, bool>));
//Now call it, first on null, then on an object
Console.WriteLine(CheckAuthorized(null)); //false
Console.WriteLine(CheckAuthorized(new User()));//true
//Wait for input so the user will actually see this.
Console.ReadKey(true);
}
}
}
哦,还有一个现实生活中的实际问题。C# 行为的好处在于,它会导致对空引用的调用快速失败,因为它们访问中间某处的字段或虚拟,所以无论如何都会失败。这意味着我们在编写调用时不必担心我们是否处于空实例中。但是,如果您想在完全公共的方法(即公共类的公共方法)中防弹,那么您不能依赖于此。如果方法的第 1 步始终跟在第 2 步之后很重要,并且只有在空实例上调用第 2 步时才会失败,那么应该进行自空检查。这很少会发生,但它可能会导致非 C# 用户的错误,如果不使用上述技术,您将永远无法在 C# 中重现这些错误。
*虽然,这是特定于他们的编译器的 - 根据 C++ 标准 IIRC,它是未定义的。