什么是空引用异常(NullReferenceException),如何修复?

1869

我有一些代码,当它执行时,抛出了一个 NullReferenceException 异常,显示:

对象引用未设置为对象的实例。

这是什么意思?我该如何修复此错误?


在VS 2017中的异常助手将更有帮助,可以诊断此异常的原因--https://blogs.msdn.microsoft.com/visualstudio/2016/11/28/productivity-in-visual-studio-2017-rc/下的“新异常助手”。 - Zev Spitz
亲爱的未来访客,这个问题的答案同样适用于ArgumentNullException。如果你的问题已被关闭并列为重复的问题,并且你正在遇到ANE,请按照答案中的指示进行调试和修复问题。 - user1228
@will,ANE 只有在传递 null 作为参数时才会发生。你能给出一个 ANE 问题被关闭,并作为此问题的副本的示例吗? - John Saunders
这个问题在Meta上出现过,但我得去找链接。至于那个评论,ANE只是NRE的一种,但有人添加了一个预防性检查,而且你至少知道哪里为空(参数名已提供),所以诊断起来比直接的NRE要容易一些。 - user1228
27个回答

11

有趣的是,本页上的答案中没有提到两个极端情况:

极端情况1:对字典进行并发访问

.NET中的通用字典不是线程安全的,当您尝试从两个并发线程访问键时,它们有时可能会抛出NullReference(空引用)或甚至更频繁地抛出KeyNotFoundException(找不到键),此时异常信息非常具有误导性。

极端情况2:不安全的代码

如果unsafe代码抛出了NullReferenceException,您可能需要查看指针变量,并检查它们是否为IntPtr.Zero或类似内容。这与“空指针异常”相同,但在不安全的代码中,变量经常被转换为值类型/数组等,您会不停地碰壁,不知道为什么值类型会引发此异常。

(顺便说一下,这也是不需要时最好不使用不安全代码的另一个原因。)

极端情况3:Visual Studio多监视器设置,辅助监视器具有与主监视器不同的DPI设置

此边缘情况与特定软件相关,涉及到Visual Studio 2019集成开发环境(IDE)(以及可能的早期版本)。

复现此问题的方法:将工具箱中的任何组件拖到具有与主监视器不同DPI设置的非主监视器上的Windows表单中,就会弹出“对象未设置为对象实例”的弹出窗口。根据这个线程,这个问题已经存在了相当长的时间,而在撰写本文时,它仍未被修复。


5
您的词典示例不是边缘情况。如果对象不是线程安全的,则从多个线程使用它会产生随机结果。您的不安全代码示例与null有何区别? - John Saunders

10

“Object reference not set to an instance of an object.”的错误行指出您尚未将实例对象分配给对象引用,但仍在访问该对象的属性/方法。

例如:假设您有一个名为myClass的类,它包含一个属性prop1。

public Class myClass
{
   public int prop1 {get;set;}
}

现在您可以像下面这样在其他类中访问此prop1:

public class Demo
{
     public void testMethod()
     {
        myClass ref = null;
        ref.prop1 = 1;  // This line throws an error
     }
}

由于声明了 myClass 类的引用,但未创建实例或未将对象的实例分配给该类的引用,因此上述代码会抛出错误。

要解决此问题,您需要实例化(将一个对象分配给该类的引用)。

public class Demo
{
     public void testMethod()
     {
        myClass ref = null;
        ref = new myClass();
        ref.prop1 = 1;
     }
}

6

简单来说:

你正在尝试访问一个未被创建或当前不在内存中的对象。

那么如何解决这个问题:

  1. Debug and let the debugger break... It will directly take you to the variable that is broken... Now your task is to simply fix this.. Using the new keyword in the appropriate place.

  2. If it is caused on some database commands because the object isn't present then all you need to do is do a null check and handle it:

    if (i == null) {
        // Handle this
    }
    
  3. The hardest one .. if the GC collected the object already... This generally occurs if you are trying to find an object using strings... That is, finding it by name of the object then it may happen that the GC might already cleaned it up... This is hard to find and will become quite a problem... A better way to tackle this is do null checks wherever necessary during the development process. This will save you a lot of time.

所谓按名称查找,是指某些框架允许您使用字符串查找对象,代码可能如下所示:FindObject("ObjectName");


3
如果您有一个对象的引用,垃圾回收器就永远不会清理它。 - John Saunders
2
如果您使用FindObject("Object Name")这样的内容,GC就无法预先知道您将引用哪个对象..这就是我试图解释的内容..这些内容发生在运行时。 - Akash Gutha
2
有一些框架可以在C#中提供这种功能,例如Unity。这个问题与BCl无关。在批评之前请先搜索互联网,有很多类似的函数,我甚至每天都在使用它们。现在请告诉我答案怎么说不通了。 - Akash Gutha
2
请查看以下链接并纠正自己,专家先生:http://docs.unity3d.com/ScriptReference/GameObject.Find.html?from=index - Akash Gutha
我在你提供的链接中看到的示例将GameObject.Find的结果分配给了一个成员字段。那是一个引用,GC在包含它的对象被收集之前不会回收它。 - John Saunders
显示剩余2条评论

6

当你尝试使用一个未实例化的类的对象时,就会出现NullReferenceException或Object reference not set to an instance of an object错误。

例如:

假设你有一个名为Student的类。

public class Student
{
    private string FirstName;
    private string LastName;
    public string GetFullName()
    {
        return FirstName + LastName;
    }
}

现在,考虑另一个类,您正在尝试检索学生的全名。
public class StudentInfo
{      
    public string GetStudentName()
    {
        Student s;
        string fullname = s.GetFullName();
        return fullname;
    }        
}

正如上面的代码所示,语句Student s - 仅声明了类型为学生(Student)的变量,请注意此时学生(Student)类未被实例化。 因此,当执行语句s.GetFullName()时,它将抛出NullReferenceException异常。

3
字面上解决NullReferenceExeption的最简单方法有两种。
例如,如果您有一个带有脚本和名为rb(刚体)的变量的GameObject,则在启动游戏时,此变量将以null开始。 这就是为什么您会收到NullReferenceExeption的原因,因为计算机没有存储该变量中的数据。
我将使用RigidBody变量作为示例。 我们可以通过以下几种方式轻松添加数据:
1.向对象添加RigidBody,使用AddComponent> Physics > Rigidbody 然后进入您的脚本并键入rb = GetComponent<Rigidbody>(); 这行代码最好在您的Start()Awake()函数下使用。
2.您可以使用一行代码编程方式添加组件并同时分配变量:rb = AddComponent<RigidBody>(); 进一步说明:如果你想让Unity向你的对象添加一个组件,但可能忘记了添加一个,你可以在类声明之前(所有using下面的空格)键入[RequireComponent(typeof(RigidBody))]
享受制作游戏的乐趣!

-5

如果在保存或编译构建过程中出现此消息,请关闭所有文件,然后打开任何文件进行编译和保存。

对我来说,原因是我已经重命名了文件,但旧文件仍然处于打开状态。


-7

这基本上是一个空引用异常。正如Microsoft所述-

当您尝试访问值为null的类型的成员时,将引发NullReferenceException异常。

这是什么意思?

这意味着如果任何成员没有任何值,并且我们让该成员执行某些任务,那么系统无疑会抛出一条消息并说-

“等等,该成员没有值,因此无法执行您交给它的任务。”

异常本身表明正在引用某些内容,但其值未被设置。因此,这表示仅在使用引用类型时才会发生,因为值类型是不可为空的。

如果我们使用值类型成员,则不会发生NullReferenceException。

class Program
{
    static void Main(string[] args)
    {
        string str = null;
        Console.WriteLine(str.Length);
        Console.ReadLine();
    }
}

以上代码显示了一个简单的字符串,它被赋予了null值。

现在,当我尝试打印字符串str的长度时,我会得到发生了未处理的异常类型‘System.NullReferenceException’的消息,因为成员str指向null,而null没有任何长度。

当我们忘记实例化引用类型时,也会发生‘NullReferenceException’。

假设我有一个类和其中的成员方法。我没有实例化我的类,只是给我的类命名。现在,如果我尝试使用这个方法,编译器将抛出一个错误或发出一个警告(取决于编译器)。

class Program
{
    static void Main(string[] args)
    {
        MyClass1 obj;
        obj.foo();  // Use of unassigned local variable 'obj'
    }
}

public class MyClass1
{
    internal void foo()
    {
        Console.WriteLine("Hello from foo");
    }
}

编译器会针对上述代码产生一个错误,指出变量obj未被分配,这意味着我们的变量具有空值或无内容。编译器会针对上述代码产生一个错误,指出变量obj未被分配,这意味着我们的变量具有空值或无内容。

为什么会发生这种情况?

  • NullReferenceException是由于我们没有检查对象的值而引起的错误。在代码开发中,我们经常不检查对象的值。

  • 当我们忘记实例化对象时,也会出现这种情况。使用可能返回或设置null值的方法、属性、集合等也可能是这个异常的原因。

如何避免这种情况?

有各种方法可以避免这个著名的异常:

  1. 显式检查:我们应该遵循检查对象、属性、方法、数组和集合是否为空的传统。这可以通过使用条件语句如if-else if-else等来简单实现。

  2. 异常处理:管理此异常的重要方法之一是使用简单的try-catch-finally块,我们可以控制此异常并维护其日志。当您的应用程序处于生产阶段时,这非常有用。

  3. 空操作符:在为对象、变量、属性和字段设置值时,空合并运算符和空条件运算符也可以派上用场。

  4. 调试器:对于开发人员,我们拥有强大的调试武器。如果在开发过程中遇到NullReferenceException,我们可以使用调试器找到异常源头。

  5. 内置方法:系统方法如GetValueOrDefault()、IsNullOrWhiteSpace()和IsNullOrEmpty()检查null并在存在null值时分配默认值。

这里已经有很多好的答案了。你也可以在我的博客上查看更详细的描述和示例。

希望这也能帮到你!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接