对空类使用方法

本文关键字:使用方法 | 更新日期: 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 的变量,它指的是什么都没有!!我的意思是,等待它,什么都没有!你能从虚无中调用方法吗?不!