对空类使用方法
本文关键字:使用方法 | 更新日期: 2023-09-27 18:28:40
假设我有一个类
class T
{
int num;
public T(int num)
{
this.num = num;
}
public void p()
{
Console.WriteLine(this.num);
}
}
我有一个空对象T t = null
.无论如何,调用该方法t.p()
都会导致 NullReferenceException。
无法测试对象是否为 null(通过使用 this == null
(检查对象在方法中是否为 null((,那么有没有办法设置一个适用于 null 对象的默认方法?
无论调用t.p()
,您都应该检查t
是否null
。不应在null
实例上调用方法。
关于问题的第二部分,不,如果该对象的实例为 null,则无法设置将调用的默认方法。
我应该指出,还有空对象模式,根据确切的使用情况可能会有所帮助。不过,我不会使用它来代替空检查。我发现该模式在某些情况下很有用。
在 C/C++ 中你可以这样做,但在 C# 中(不容易(做到这一点。
例如,您可能希望检查"字符串是否为空">或"集合是否为空",并将 null 对象视为"空"。对于这种情况,能够通过空指针/引用调用IsEmpty()
方法会产生看起来更优雅/可读的代码:
if (myList.IsEmpty())
...
而不是
if (myList == null || myList.IsEmpty())
...
但是,它也非常危险,因为它隐藏了异常/意外的行为,并鼓励调用方停止检查空值。由于您无法保证调用的每个方法都会保护您免受 null 的影响,因此您如何知道停止检查调用代码中的 null 是安全的?或者你检查 null 两次(在调用和被调用代码中(,以防万一?如果您在同一个对象实例上调用多个方法,那么在开始时检查一次 null 不是更有效吗?
因此,尽管此模式可用于某些语言,但它不被认为是一种好的做法。
这就是为什么在 C# 中不允许使用此类代码的(原因之一(。相反,首选模式是提供静态方法来检查状态:
if (string.IsNullOrEmpty(myString))
....
这只是可读性稍差,但更安全,因为空检查对读者来说是清晰可见的。即使标题中没有"IsNull",tou 也可以看到引用正在传递给另一个方法,并且更合理地假设被调用的方法会写得很好并检查 null。透明和明确使代码更容易被其他人阅读。
C# 还提供了空合并??
运算符,以使调用代码中的空检查更干净。如果您希望在对象为 null 时使用默认值,则可以使用以下形式:
string result = myString ?? string.Empty;
。在这种情况下,如果它是非 null 或字符串,则将返回 myString
。如果为空,则为空,从而保证result
永远不会为空。
最后,在 C# 6 中,您可以使用 ?.
运算符,该运算符将"如果不是 null"检查与访问相结合,因此可以替换:
if (element != null && element.FirstChild != null)
element.FirstChild.DoSomething();
跟
element?.FirstChild?.DoSomething();
这在功能上是等效的,但是一种更紧凑的语法。
在null
引用上调用实例方法将始终导致NullReferenceException
。您必须检查引用是否未null
或使用 C# 6 ?.
运算符为您执行此操作。
T t = null;
t?.p(); // no method is called
但是,可以在null
引用上使用扩展方法。它有一些限制,因为您只能在扩展方法中访问公共成员,但可能很有用(特别是如果您无法使用 C# 6 和 ?.
(:
class T
{
public int Number { get; private set; } // public property
public T(int number)
{
Number = number;
}
}
static class Extensions
{
public static void PrintT(this T t)
{
if (t == null) Console.WriteLine("null");
else Console.WriteLine(t.Number);
}
}
T t = null;
t.PrintT(); // no exception, writes "null" to the console
可以在 CIL 中的null
引用上调用实例方法,只需使用 call
而不是 callvirt
即可。后者检查引用对象的运行时类型,这就是它立即抛出 NRE 的原因。 call
应该正常调用该方法,但隐式this
参数设置为 null,并在首次访问 this
的任何成员时抛出。
编辑
C# 中的 System.Reflection.Emit
命名空间包含允许您动态发出 IL 的类型。您可以使用它来调用带有 call
而不是 callvirt
的实例方法。(我修改了此答案中发布的代码以适用于方法。此处的示例仅适用于无参数方法(
public static void MakeNonVirtualCall<T>(T c, Expression<Action<T>> f)
{
var expression = f.Body as MethodCallExpression;
if (expression == null) throw new ArgumentException();
var dyn = new DynamicMethod("NVCall", null, new[] { typeof(T) }, typeof(T).Module, true);
var il = dyn.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, expression.Method);
il.Emit(OpCodes.Ret);
((Action<T>)dyn.CreateDelegate(typeof(Action<T>)))(c);
}
现在,假设我们有一个类
class Class
{
public void Method()
{
if (this == null) Console.WriteLine("`this` is null");
else Console.WriteLine("`this` is not null");
}
}
我们可以将其用作
Class nullRef = null;
Class instance = new Class();
MakeNonVirtualCall(nullRef, t => t.Method()); // no exception, prints "`this` is null"
MakeNonVirtualCall(instance, t => t.Method()); // prints "`this` is not null"
它可以工作 - C# 类中的实例方法可以在 IL 的null
引用上调用(但仍然不能直接从 C# 调用(。
请注意,此处的 MSDN 文档不正确:
对实例(或虚拟(方法的调用必须在任何用户可见参数之前推送该实例引用。实例引用不得为空引用。
只需检查调用代码中的null
。
T t = null;
// somewhere further..
if (t == null)
//t is null, don't use it
else
//save to use t
另一种方法(如果您不希望对象被null
是使用 try
/catch
(:
T t = null;
// somewhere further..
try {
t.p();
}
catch(NullReferenceException ex) {
// t is null
}
不,你不能。我会告诉你为什么。关键字 this
是指正在访问变量或调用方法的任何对象。因此,doSomething()
内部的this
将指object.doSomething()
中的object
。现在来谈谈你关于null
的问题,当你写T t = null;
时,这意味着你做了一个类型为 T
的变量,它指的是什么都没有!!我的意思是,等待它,什么都没有!你能从虚无中调用方法吗?不!