什么是 NullReferenceException,如何修复它?

What is a NullReferenceException, and how do I fix it?

提问人: 提问时间:1/12/2011 最后编辑:22 revs, 16 users 40%John Saunders 更新时间:9/16/2022 访问量:2180008

问:

这个问题的答案是社区的努力。编辑现有答案以改进此帖子。它目前不接受新的答案或交互。

我有一些代码,当它执行时,它会抛出一个 ,说:NullReferenceException

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

这是什么意思,我能做些什么来修复这个错误?

C# .NET vb.net null nullreferenceexception

评论

0赞 Zev Spitz 12/29/2016
VS 2017 中的异常帮助程序在诊断此异常的原因时会更有帮助 -- blogs.msdn.microsoft.com/visualstudio/2016/11/28/...“新建异常帮助程序”下。
0赞 10/14/2017
亲爱的未来访客,这个问题的答案同样适用于 ArgumentNullException。如果您的问题已作为此问题的重复问题关闭,并且您遇到 ANE,请按照答案中的说明调试和修复您的问题。
0赞 John Saunders 10/15/2017
@will 仅当 null 作为参数传递时,ANE 才应发生。如果一个 ANE 问题作为这个问题的重复而关闭,您能举个例子吗?
0赞 10/16/2017
它出现在 Meta 上,但我必须去挖掘链接。但就该注释而言,ANE 只是一个 NRE,但有人添加了一个先发制人的检查,并且您至少确切地知道什么是 null(提供了参数名称),因此它比直接的 NRE 更容易诊断。

答:

174赞 Jonathan Wood #1

这意味着您的代码使用了设置为 null 的对象引用变量(即它没有引用实际的对象实例)。

为防止出现此错误,应在使用之前对可能为 null 的对象进行 null 测试。

if (myvar != null)
{
    // Go ahead and use myvar
    myvar.property = ...
}
else
{
    // Whoops! myvar is null and cannot be used without first
    // assigning it to an instance reference
    // Attempting to use myvar here will result in NullReferenceException
}
170赞 5 revs, 3 users 74%Chris B. Behrens #2

这意味着所讨论的变量是指向任何对象的。我可以像这样生成这个:

SqlConnection connection = null;
connection.Open();

这将引发错误,因为虽然我声明了变量“”,但它没有指向任何内容。当我尝试调用成员“”时,没有要解析的引用,并且它会抛出错误。connectionOpen

要避免此错误,请执行以下操作:

  1. 在尝试对对象执行任何操作之前,请始终初始化它们。
  2. 如果不确定对象是否为 null,请使用 进行检查。object == null

JetBrains 的 ReSharper 工具将识别代码中可能出现空引用错误的每个位置,从而允许您进行空值检查。恕我直言,此错误是错误的头号来源。

评论

3赞 j riv 1/21/2018
JetBrains 的 Resharper 工具将识别代码中可能出现空引用错误的每个位置。这是不正确的。我有一个没有该检测的解决方案,但代码偶尔会导致异常。我怀疑在涉及多线程时,它有时无法检测到 - 至少被他们检测到,但我不能进一步评论,因为我还没有确定我的错误的位置。
1赞 Sunny Sandeep 2/2/2018
但是当 NullReferenceException 出现在 usign HttpContext.Current.Responce.Clear() 中时如何解决它。上述任何解决方案都无法解决。因为在创建其 HttpContext 对象时,会出现错误“重载解析失败,因为没有可访问的'New'接受此参数数。
2709赞 55 revs, 39 users 26%LopDev #3

原因是什么?

底线

您正在尝试使用(或 VB.NET)的东西。这意味着您要么将其设置为 ,要么根本不将其设置为任何内容。nullNothingnull

像其他任何事情一样,被传递。如果它在方法“A”,则可能是方法“B”将 a 传递给方法“A”。nullnullnull

null可以有不同的含义:

  1. 未初始化的对象变量,因此不指向任何内容。在这种情况下,如果访问此类对象的成员,则会导致 .NullReferenceException
  2. 开发人员有意使用 null 来指示没有可用的有意义的值。请注意,C# 具有变量的可为 null 数据类型的概念(就像数据库表可以有可为 null 的字段一样)——您可以赋值给它们以指示其中没有存储值,例如(这是 的快捷方式),其中问号表示允许它存储在变量中。您可以使用 或 进行检查。可为 null 的变量,如本例所示,允许通过显式访问值,或者像正常一样通过 .
    请注意,通过抛出而不是 if is 来访问它 - 您应该事先进行检查,即如果您有另一个不可为 null 的变量,那么您应该执行类似或更短的赋值。
    nullint? a = null;Nullable<int> a = null;nullaif (a.HasValue) {...}if (a==null) {...}aa.Valueaa.ValueInvalidOperationExceptionNullReferenceExceptionanullint b;if (a.HasValue) { b = a.Value; }if (a != null) { b = a; }

本文的其余部分将更详细地介绍许多程序员经常犯的错误,这些错误可能导致 .NullReferenceException

更具体地说

抛出 a 总是意味着同样的事情:您正在尝试使用引用,并且该引用未初始化(或者它曾经初始化过,但不再初始化)。runtimeNullReferenceException

这意味着引用是 ,并且您不能通过引用访问成员(例如方法)。最简单的情况:nullnull

string foo = null;
foo.ToUpper();

这将在第二行抛出 a,因为您无法在指向 的引用上调用实例方法。NullReferenceExceptionToUpper()stringnull

调试

你如何找到一个的来源?除了查看异常本身(将精确地在异常发生的位置引发)之外,Visual Studio 中调试的一般规则也适用:放置战略断点并检查变量,方法是将鼠标悬停在变量名称上、打开“快速”监视窗口或使用各种调试面板(如“局部变量”和“自动”)。NullReferenceException

如果要找出引用的位置,请右键单击其名称,然后选择“查找所有引用”。然后,可以在每个找到的位置放置一个断点,并在附加调试器的情况下运行程序。每次调试器在此类断点中断时,都需要确定是否期望引用为非 null,检查变量,并验证它是否在预期时指向实例。

通过以这种方式遵循程序流,您可以找到实例不应为 null 的位置,以及未正确设置实例的原因。

例子

可能引发异常的一些常见情况:

通用

ref1.ref2.ref3.member

如果 ref1 或 ref2 或 ref3 为 null,则将得到一个 .如果要解决问题,则通过将表达式重写为更简单的等价物来找出哪个是空值:NullReferenceException

var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member

具体来说,在 中,可以是 null,或者属性可以是 null,或者属性可以是 null。HttpContext.Current.User.Identity.NameHttpContext.CurrentUserIdentity

间接

public class Person 
{
    public int Age { get; set; }
}
public class Book 
{
    public Person Author { get; set; }
}
public class Example 
{
    public void Foo() 
    {
        Book b1 = new Book();
        int authorAge = b1.Author.Age; // You never initialized the Author property.
                                       // there is no Person to get an Age from.
    }
}

如果要避免子 (Person) 空引用,可以在父 (Book) 对象的构造函数中初始化它。

嵌套对象初始值设定项

这同样适用于嵌套对象初始值设定项:

Book b1 = new Book 
{ 
   Author = { Age = 45 } 
};

这意味着:

Book b1 = new Book();
b1.Author.Age = 45;

使用关键字时,它只创建 的新实例 ,而不创建 的新实例 ,因此属性仍然是 。newBookPersonAuthornull

嵌套集合初始值设定项

public class Person 
{
    public ICollection<Book> Books { get; set; }
}
public class Book 
{
    public string Title { get; set; }
}

嵌套集合的行为相同:Initializers

Person p1 = new Person 
{
    Books = {
         new Book { Title = "Title1" },
         new Book { Title = "Title2" },
    }
};

这意味着:

Person p1 = new Person();
p1.Books.Add(new Book { Title = "Title1" });
p1.Books.Add(new Book { Title = "Title2" });

仅创建 的实例,但集合仍为 。集合语法不会创建集合 对于 ,它只转换为语句。new PersonPersonBooksnullInitializerp1.Booksp1.Books.Add(...)

数组

int[] numbers = null;
int n = numbers[0]; // numbers is null. There is no array to index.

数组元素

Person[] people = new Person[5];
people[0].Age = 20 // people[0] is null. The array was allocated but not
                   // initialized. There is no Person to set the Age for.

锯齿状阵列

long[][] array = new long[1][];
array[0][0] = 3; // is null because only the first dimension is yet initialized.
                 // Use array[0] = new long[2]; first.

集合/列表/字典

Dictionary<string, int> agesForNames = null;
int age = agesForNames["Bob"]; // agesForNames is null.
                               // There is no Dictionary to perform the lookup.

范围变量(间接/延迟)

public class Person 
{
    public string Name { get; set; }
}
var people = new List<Person>();
people.Add(null);
var names = from p in people select p.Name;
string firstName = names.First(); // Exception is thrown here, but actually occurs
                                  // on the line above.  "p" is null because the
                                  // first element we added to the list is null.

事件 (C#)

public class Demo
{
    public event EventHandler StateChanged;
    
    protected virtual void OnStateChanged(EventArgs e)
    {        
        StateChanged(this, e); // Exception is thrown here 
                               // if no event handlers have been attached
                               // to StateChanged event
    }
}

(注意:VB.NET 编译器会插入 null 检查事件使用情况,因此无需检查事件中的 VB.NET。Nothing

错误的命名约定:

如果字段的命名方式与局部变量不同,则可能已意识到从未初始化过该字段。

public class Form1
{
    private Customer customer;
    
    private void Form1_Load(object sender, EventArgs e) 
    {
        Customer customer = new Customer();
        customer.Name = "John";
    }
    
    private void Button_Click(object sender, EventArgs e)
    {
        MessageBox.Show(customer.Name);
    }
}

这可以通过遵循在字段前面加上下划线的约定来解决:

    private Customer _customer;

ASP.NET 页面生命周期:

public partial class Issues_Edit : System.Web.UI.Page
{
    protected TestIssue myIssue;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
             // Only called on first load, not when button clicked
             myIssue = new TestIssue(); 
        }
    }
        
    protected void SaveButton_Click(object sender, EventArgs e)
    {
        myIssue.Entry = "NullReferenceException here!";
    }
}

ASP.NET 会话值

// if the "FirstName" session value has not yet been set,
// then this line will throw a NullReferenceException
string firstName = Session["FirstName"].ToString();

ASP.NET MVC 空视图模型

如果在引用 的属性时发生异常,则需要了解 在操作方法中设置,当您查看时。当您从控制器返回空模型(或模型属性)时,当视图访问它时会发生异常:@ModelASP.NET MVC ViewModelreturn

// Controller
public class Restaurant:Controller
{
    public ActionResult Search()
    {
        return View();  // Forgot the provide a Model here.
    }
}

// Razor view 
@foreach (var restaurantSearch in Model.RestaurantSearch)  // Throws.
{
}
    
<p>@Model.somePropertyName</p> <!-- Also throws -->

WPF 控件创建顺序和事件

WPF控件在调用期间按照它们在可视化树中的显示顺序创建。如果早期创建的控件具有事件处理程序等,则会引发 A,在此期间会引用后期创建的控件。InitializeComponentNullReferenceExceptionInitializeComponent

例如:

<Grid>
    <!-- Combobox declared first -->
    <ComboBox Name="comboBox1" 
              Margin="10"
              SelectedIndex="0" 
              SelectionChanged="comboBox1_SelectionChanged">
       <ComboBoxItem Content="Item 1" />
       <ComboBoxItem Content="Item 2" />
       <ComboBoxItem Content="Item 3" />
    </ComboBox>
        
    <!-- Label declared later -->
    <Label Name="label1" 
           Content="Label"
           Margin="10" />
</Grid>

这里是在之前创建的。如果尝试引用“label1”,则尚未创建它。comboBox1label1comboBox1_SelectionChanged

private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    label1.Content = comboBox1.SelectedIndex.ToString(); // NullReferenceException here!!
}

更改声明的顺序(即,在前面列出,忽略设计哲学的问题)至少可以解决这里的问题。XAMLlabel1comboBox1NullReferenceException

投射as

var myThing = someObject as Thing;

这不会抛出 an,而是在强制转换失败时返回 a(并且 when 本身为 null)。所以要注意这一点。InvalidCastExceptionnullsomeObject

LINQ 和FirstOrDefault()SingleOrDefault()

普通版本,并在没有任何内容时抛出异常。在这种情况下,“OrDefault”版本返回。所以要注意这一点。First()Single()null

foreach

foreach当您尝试迭代集合时引发。通常是由返回集合的方法的意外结果引起的。nullnull

List<int> list = null;    
foreach(var v in list) { } // NullReferenceException here

更现实的例子 - 从XML文档中选择节点。如果未找到节点,则抛出,但初始调试显示所有属性都有效:

foreach (var node in myData.MyXml.DocumentNode.SelectNodes("//Data"))

避免的方法

显式检查并忽略值。nullnull

如果您希望引用有时是 ,则可以在访问实例成员之前检查它是否存在:nullnull

void PrintName(Person p)
{
    if (p != null) 
    {
        Console.WriteLine(p.Name);
    }
}

显式检查并提供默认值。null

您调用的需要实例的方法可以返回,例如,当找不到要查找的对象时。在这种情况下,您可以选择返回默认值:null

string GetCategory(Book b) 
{
    if (b == null)
        return "Unknown";
    return b.Category;
}

显式检查 from 方法调用并引发自定义异常。null

还可以抛出自定义异常,仅在调用代码中捕获它:

string GetCategory(string bookTitle) 
{
    var book = library.FindBook(bookTitle);  // This may return null
    if (book == null)
        throw new BookNotFoundException(bookTitle);  // Your custom exception
    return book.Category;
}

如果值不应为 ,则使用 ,以便在异常发生之前捕获问题。Debug.Assertnull

当您在开发过程中知道某个方法可以返回但永远不应该返回时,您可以使用在它确实发生时尽快中断:nullDebug.Assert()

string GetTitle(int knownBookID) 
{
    // You know this should never return null.
    var book = library.GetBook(knownBookID);  

    // Exception will occur on the next line instead of at the end of this method.
    Debug.Assert(book != null, "Library didn't return a book for known book ID.");

    // Some other code

    return book.Title; // Will never throw NullReferenceException in Debug mode.
}

尽管此检查不会最终出现在您的发布版本中,但会导致它在运行时处于发布模式时再次抛出。NullReferenceExceptionbook == null

用于值类型,以便在 时提供默认值。GetValueOrDefault()nullablenull

DateTime? appointment = null;
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the default value provided (DateTime.Now), because appointment is null.

appointment = new DateTime(2022, 10, 20);
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the appointment date, not the default

使用 null 合并运算符:[C#] 或 [VB]。??If()

遇到 a 时提供默认值的简写:null

IService CreateService(ILogger log, Int32? frobPowerLevel)
{
   var serviceImpl = new MyService(log ?? NullLog.Instance);
 
   // Note that the above "GetValueOrDefault()" can also be rewritten to use
   // the coalesce operator:
   serviceImpl.FrobPowerLevel = frobPowerLevel ?? 5;
}

对数组使用 null 条件运算符: 或(在 C# 6 和 VB.NET 14 中可用):?.?[x]

这有时也被称为安全导航或猫王(以其形状命名)运算符。如果运算符左侧的表达式为 null,则不会计算右侧的表达式,而是返回 null。这意味着这样的情况:

var title = person.Title.ToUpper();

如果此人没有标题,这将引发异常,因为它正在尝试调用具有 null 值的属性。ToUpper

在内部和下面,可以通过以下方式保护:C# 5

var title = person.Title == null ? null : person.Title.ToUpper();

现在,title 变量将为 null,而不是引发异常。C# 6 为此引入了一个更短的语法:

var title = person.Title?.ToUpper();

这将导致 title 变量为 ,如果为 ,则不调用 。nullToUpperperson.Titlenull

当然,您仍然必须检查或使用 null 条件运算符和 null 合并运算符 () 来提供默认值:titlenull??

// regular null check
int titleLength = 0;
if (title != null)
    titleLength = title.Length; // If title is null, this would throw NullReferenceException
    
// combining the `?` and the `??` operator
int titleLength = title?.Length ?? 0;

同样,对于数组,您可以按如下方式使用:?[i]

int[] myIntArray = null;
var i = 5;
int? elem = myIntArray?[i];
if (!elem.HasValue) Console.WriteLine("No value");

这将执行以下操作: 如果 是 ,则表达式返回,您可以安全地检查它。如果它包含一个数组,它将执行与以下相同的操作: 并返回第 i 个元素。myIntArraynullnullelem = myIntArray[i];

使用 null 上下文(在 C# 8 中可用):

中引入的 null 上下文和可为 null 的引用类型对变量执行静态分析,如果值可能或已设置为 ,则提供编译器警告。可为 null 的引用类型允许显式允许类型为 。C# 8nullnullnull

可以使用文件中的元素为项目设置可为 null 的注释上下文和可为 null 的警告上下文。此元素配置编译器如何解释类型的可为 null 性以及生成哪些警告。有效设置包括:Nullablecsproj

  • enable:启用可为 null 的注释上下文。启用可为 null 的警告上下文。例如,引用类型(字符串)的变量不可为 null。启用所有可为 null 性警告。
  • disable:禁用可为 null 的注释上下文。可为 null 的警告上下文被禁用。引用类型的变量是被遗忘的,就像早期版本的 C# 一样。禁用所有可为 null 性警告。
  • safeonly:启用可为 null 的注释上下文。可为 null 的警告上下文是 safeonly。引用类型的变量不可为 null。所有安全可空性警告均已启用。
  • warnings:禁用可为 null 的注释上下文。启用可为 null 的警告上下文。引用类型的变量是忽略的。启用所有可为 null 性警告。
  • safeonlywarnings:禁用可为 null 的注释上下文。可为 null 的警告上下文是 safeonly。 引用类型的变量是忽略的。所有安全可空性警告均已启用。

使用与可为 null 的值类型相同的语法来记录可为 null 的引用类型:将 a 追加到变量的类型中。?

用于调试和修复迭代器中 null deref 的特殊技术

C#支持“迭代器块”(在其他一些流行语言中称为“生成器”)。 由于延迟执行,在迭代器块中调试可能特别棘手:NullReferenceException

public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
    for (int i = 0; i < count; ++i)
    yield return f.MakeFrob();
}
...
FrobFactory factory = whatever;
IEnumerable<Frobs> frobs = GetFrobs();
...
foreach(Frob frob in frobs) { ... }

如果结果在,那么会抛出。现在,您可能会认为正确的做法是:whatevernullMakeFrob

// DON'T DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
   if (f == null) 
      throw new ArgumentNullException("f", "factory must not be null");
   for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}

为什么这是错误的?因为迭代器块实际上并没有运行,直到 !调用 只是返回一个对象,该对象在迭代时将运行迭代器块。foreachGetFrobs

通过编写这样的检查,您可以防止 ,但您将 移动到迭代点,而不是调用点,这非常令人困惑nullNullReferenceExceptionNullArgumentException

正确的解决方法是:

// DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
   // No yields in a public method that throws!
   if (f == null) 
       throw new ArgumentNullException("f", "factory must not be null");
   return GetFrobsForReal(f, count);
}
private IEnumerable<Frob> GetFrobsForReal(FrobFactory f, int count)
{
   // Yields in a private method
   Debug.Assert(f != null);
   for (int i = 0; i < count; ++i)
        yield return f.MakeFrob();
}

也就是说,创建一个具有迭代器块逻辑的私有帮助程序方法,以及一个执行检查并返回迭代器的公共图面方法。现在,当被调用时,检查会立即发生,然后在迭代序列时执行。nullGetFrobsnullGetFrobsForReal

如果检查对象的引用源,您将看到此技术自始至终都在使用。它编写起来稍微笨拙一些,但它使调试无效错误变得更加容易。优化代码是为了方便调用方,而不是为了方便作者LINQ

关于不安全代码中的空取消引用的说明

C#具有“不安全”模式,顾名思义,这是极其危险的,因为提供内存安全和类型安全的正常安全机制没有得到强制执行。除非你对内存的工作原理有透彻而深入的了解,否则你不应该编写不安全的代码

在不安全模式下,您应该注意两个重要事实:

  • 取消引用 null 指针会产生与取消引用 null 引用相同的异常
  • 在某些情况下,取消引用无效的非 null 指针可能会产生该异常

要理解为什么会这样,首先了解 .NET 是如何生成的会有所帮助。(这些详细信息适用于在 Windows 上运行的 .NET;其他操作系统使用类似的机制。NullReferenceException

内存在 中虚拟化 ;每个进程都会获得一个由操作系统跟踪的许多“页”内存的虚拟内存空间。内存的每一页都设置了标志,用于确定如何使用它:读取、写入、执行等。最低的页面被标记为“如果以任何方式使用,则会产生错误”。Windows

空指针和空引用在内部都表示为数字零,因此任何将其取消引用到其相应内存存储中的尝试都会导致操作系统产生错误。然后,.NET 运行时检测到此错误并将其转换为 .C#NullReferenceException

这就是为什么取消引用 null 指针和 null 引用会产生相同的异常。

第二点呢?取消引用位于虚拟内存最低页的任何无效指针会导致相同的操作系统错误,从而导致相同的异常。

为什么这是有道理的?好吧,假设我们有一个包含两个 int 的结构,以及一个等于 null 的非托管指针。如果我们尝试取消引用结构中的第二个 int,则不会尝试访问位置 0 处的存储;它将访问位置 4 的存储。但从逻辑上讲,这是一个 null 取消引用,因为我们是通过 null 到达该地址的。CLR

如果您正在使用不安全的代码,并且得到一个 ,请注意,有问题的指针不必为 null。它可以是最低页面中的任何位置,并且将产生此异常。NullReferenceException

评论

64赞 JPK 5/20/2014
也许这是一个愚蠢的评论,但避免这个问题的第一个也是最好的方法不是初始化对象吗?对我来说,如果发生此错误,通常是因为我忘记初始化数组元素之类的东西。我认为将对象定义为 null 然后引用它的情况要少得多。也许给出解决描述旁边每个问题的方法。还是一篇好文章。
34赞 John Saunders 5/20/2014
如果没有对象,而是方法或属性的返回值,该怎么办?
8赞 9/9/2014
书/作者的例子有点奇怪......这到底是怎么编译的?智能感知是如何工作的?这是什么我不擅长计算机...
5赞 John Saunders 9/9/2014
@Will:我上次的编辑有帮助吗?如果没有,那么请更明确地说明您认为的问题。
6赞 9/9/2014
@JohnSaunders 哦,不,对不起,我的意思是对象初始值设定项版本。 内部初始化如何...我想不出内部 init 会起作用的情况,但它可以编译和智能感知......除非结构?new Book { Author = { Age = 45 } };
103赞 code master #4

请注意,无论哪种情况,原因在 .NET 中始终是相同的:

您正在尝试使用值为 / 的引用变量。当引用变量的值为 / 时,这意味着它实际上并不包含对堆上存在的任何对象的实例的引用。NothingnullNothingnull

您要么从未为变量赋值,要么从未创建赋值给变量的值的实例,要么手动将变量设置为 /,要么调用了将变量设置为 / 的函数。NothingnullNothingnull

94赞 4 revs, 3 users 71%Alex KeySmith #5

抛出此异常的一个例子是:当您尝试检查某些内容时,该值为 null。

例如:

string testString = null; //Because it doesn't have a value (i.e. it's null; "Length" cannot do what it needs to do)

if (testString.Length == 0) // Throws a nullreferenceexception
{
    //Do something
} 

当您尝试对尚未实例化的内容(即上面的代码)执行操作时,.NET 运行时将引发 NullReferenceException。

与 ArgumentNullException 相比,如果方法期望传递给它的内容不为 null,则通常作为防御措施引发。

有关详细信息,请参阅 C# NullReferenceException 和 Null 参数

232赞 7 revs, 4 users 79%Simon Mourier #6

另一种情况是将 null 对象强制转换为值类型。例如,下面的代码:

object o = null;
DateTime d = (DateTime)o;

它会在演员表上抛出一个。在上面的示例中,这似乎很明显,但这可能发生在更“后期绑定”的复杂场景中,其中 null 对象是从您不拥有的某些代码返回的,并且强制转换是由某些自动系统生成的。NullReferenceException

例如,这个简单的 ASP.NET String 片段与 Calendar 控件的绑定:

<asp:Calendar runat="server" SelectedDate="<%#Bind("Something")%>" />

这里,实际上是 Web 控件类型的属性(类型),绑定可以完美地返回 null 值。隐式 ASP.NET 生成器将创建一段代码,该代码将等效于上面的强制转换代码。这将引发一个很难发现的,因为它存在于生成的代码中 ASP.NET 这些代码编译得很好......SelectedDateDateTimeCalendarNullReferenceException

评论

7赞 Serge Shultz 6/29/2015
很棒的收获。避免单行方法:DateTime x = (DateTime) o as DateTime? ?? defaultValue;
75赞 user1814380 #7

您正在使用包含 null 值引用的对象。所以它给出了一个 null 异常。在此示例中,字符串值为 null,在检查其长度时,发生了异常。

例:

string value = null;
if (value.Length == 0) // <-- Causes exception
{
    Console.WriteLine(value); // <-- Never reached
}

异常错误为:

未处理的异常:

System.NullReferenceException:对象引用未设置为实例 对象。在 Program.Main()

评论

1赞 samus 8/1/2013
多么深刻!我从不认为“null”常量是参考值。所以这就是 C# 抽象“NullPointer”的方式吧?我记得在 C++ 中,NPE 可能是由于取消引用未初始化的指针(即 c# 中的 ref 类型)引起的,其默认值恰好是未分配给该进程的地址(在许多情况下,这将是 0,尤其是在执行自动初始化的更高版本的 C++ 中,它属于操作系统 - f 与它并死 beotch(或者只是捕获操作系统攻击您的进程的 sigkill))。
84赞 2 revs, 2 users 89%Jonathon Reinhart #8

另一种可能发生的情况是 as 运算符的(不正确)使用:NullReferenceExceptions

class Book {
    public string Name { get; set; }
}
class Car { }

Car mycar = new Car();
Book mybook = mycar as Book;   // Incompatible conversion --> mybook = null

Console.WriteLine(mybook.Name);   // NullReferenceException

这里,和是不兼容的类型;无法将 a 转换/转换为 .当此强制转换失败时,返回 .在此之后使用会导致 .BookCarCarBookasnullmybookNullReferenceException

通常,应使用强制转换或 ,如下所示:as

如果你希望类型转换总是成功(即,你提前知道对象应该是什么),那么你应该使用强制转换:

ComicBook cb = (ComicBook)specificBook;

如果您不确定类型,但想尝试将其用作特定类型,请使用:as

ComicBook cb = specificBook as ComicBook;
if (cb != null) {
   // ...
}

评论

2赞 Brendan 2/19/2014
拆箱变量时,这种情况经常发生。我发现在我更改了 UI 元素的类型但忘记更新代码隐藏后,它经常发生在事件处理程序中。
93赞 12 revs, 4 users 87%Fabian Bigler #9

2019 年 C#8.0 更新:可为 null 的引用类型

C#8.0 引入了可为 null 的引用类型和不可为 null 的引用类型。因此,必须仅检查可为 null 的引用类型以避免 NullReferenceException


如果尚未初始化引用类型,并且想要设置或读取其属性之一,则它将引发 NullReferenceException

例:

Person p = null;
p.Name = "Harry"; // NullReferenceException occurs here.

您可以通过检查变量是否不为 null 来避免这种情况:

Person p = null;
if (p!=null)
{
    p.Name = "Harry"; // Not going to run to this point
}

若要完全理解引发 NullReferenceException 的原因,请务必了解值类型和 [引用类型][3] 之间的区别。

因此,如果要处理值类型,则不会发生 NullReferenceExceptions。虽然在处理引用类型时需要保持警惕!

顾名思义,只有引用类型才能保存引用或从字面上指向任何内容(或“null”)。而值类型始终包含值。

引用类型(必须检查这些类型):

  • 动态
  • 对象
  • 字符串

值类型(您可以简单地忽略这些类型):

  • 数值类型
  • 整体类型
  • 浮点类型
  • 十进制
  • 布尔值
  • 用户定义的结构

评论

6赞 John Saunders 5/17/2013
-1:由于问题是“什么是 NullReferenceException”,因此值类型无关紧要。
22赞 Fabian Bigler 5/17/2013
@John桑德斯:我不同意。作为软件开发人员,能够区分值和引用类型非常重要。否则,人们最终会检查整数是否为空。
5赞 John Saunders 5/17/2013
没错,只是不是在这个问题的背景下。
4赞 Fabian Bigler 5/17/2013
谢谢你的提示。我稍微改进了一下,并在顶部添加了一个示例。我仍然认为提及引用和值类型是有用的。
5赞 John Saunders 5/19/2013
我认为您没有添加其他答案中没有的任何内容,因为该问题预先假定了引用类型。
45赞 3 revsJohn Saunders #10

可能收到此异常的另一种一般情况是在单元测试期间模拟类。无论使用哪种模拟框架,都必须确保正确模拟类层次结构的所有适当级别。特别是,必须模拟被测代码引用的所有属性。HttpContext

有关比较详细的示例,请参阅“测试自定义 AuthorizationAttribute 时引发的 NullReferenceException”。

46赞 3 revsAbhinavRanjan #11

当实体框架中使用的实体的类名与 Web 窗体代码隐藏文件的类名相同时,添加大小写。

假设您有一个 Web 窗体 Contact.aspx,其代码隐藏类为 Contact,并且您有一个实体名称 Contact。

然后,以下代码将在调用 context 时引发 NullReferenceException。保存更改()

Contact contact = new Contact { Name = "Abhinav"};
var context = new DataContext();
context.Contacts.Add(contact);
context.SaveChanges(); // NullReferenceException at this line

为了完整起见,DataContext 类

public class DataContext : DbContext 
{
    public DbSet<Contact> Contacts {get; set;}
}

和 Contact 实体类。有时实体类是分部类,因此您也可以在其他文件中扩展它们。

public partial class Contact 
{
    public string Name {get; set;}
}

当实体类和代码隐藏类位于同一命名空间中时,会发生该错误。 若要解决此问题,请重命名 Contact.aspx 的实体类或代码隐藏类。

原因我仍然不确定原因。但是,每当任何实体类扩展 System.Web.UI.Page 就会发生此错误。

有关讨论,请查看 DbContext.saveChanges() 中的 NullReferenceException

70赞 10 revs, 2 users 78%user2864740 #12

虽然导致 NullReferenceExceptions 的原因以及避免/修复此类异常的方法已在其他答案中得到解决,但许多程序员尚未学会如何在开发过程中独立调试此类异常。

在 Visual Studio 中,这通常很容易,这要归功于 Visual Studio 调试器


首先,确保捕获正确的错误 - 请参阅如何在 VS2010 中允许中断“System.NullReferenceException”? 1

然后,从调试开始 (F5)将 [VS 调试器] 附加到正在运行的进程。有时,使用 Debugger.Break 可能很有用,它会提示启动调试器。

现在,当引发(或未处理)NullReferenceException时,调试器将在发生异常的行上停止(还记得上面设置的规则吗?有时错误很容易被发现。

例如 在以下行中,唯一可能导致异常的代码是 if 计算结果为 null。这可以通过查看“监视窗口”或在“即时窗口”中运行表达式来验证。myString

var x = myString.Trim();

在更高级的情况下(例如,如下所示),需要使用上述技术之一(监视或即时窗口)来检查表达式,以确定是否为 null 或是否为 null。str1str2

var x = str1.Trim() + str2.Trim();

一旦找到了抛出异常的位置,通常很容易向后推理以找出 null 值是 [错误地] 引入的位置——

花点时间了解异常的原因。检查 null 表达式。检查可能导致此类 null 表达式的先前表达式。根据需要添加断点并单步执行程序。使用调试器。


1 如果 Break on Throws 过于激进,并且调试器在 .NET 或第三方库中的 NPE 上停止,则 Break on User-Unhandled 可用于限制捕获的异常。此外,VS2012 还引入了 Just My Code,我建议也启用它。

如果在启用“仅我的代码”的情况下进行调试,则行为会略有不同。启用“仅我的代码”后,调试器将忽略在“我的代码”外部引发且不通过“我的代码”传递的首次机会公共语言运行时 (CLR) 异常

40赞 Hemant Bavle #13

当我们尝试访问 null 对象的 Properties 时,或者当字符串值变为空并且我们尝试访问字符串方法时,会抛出 A。NullReferenceException

例如:

  1. 当访问空字符串的字符串方法时:

    string str = string.Empty;
    str.ToLower(); // throw null reference exception
    
  2. 当访问 null 对象的属性时:

    Public Class Person {
        public string Name { get; set; }
    }
    Person objPerson;
    objPerson.Name  /// throw Null refernce Exception 
    

评论

3赞 Kjartan 7/24/2015
这是不正确的。 不会抛出 null 引用异常。它表示一个实际的字符串,尽管是一个空字符串(即)。由于这有一个要调用的对象,因此在那里抛出 null 引用异常是没有意义的。String.Empty.ToLower()""ToLower()
44赞 4 revs, 3 users 72%Mukus #14

我有不同的观点来回答这个问题。这种回答是“我还能做些什么来避免它?"

当跨不同层工作时,例如在 MVC 应用程序中,控制器需要服务来调用业务操作。在这种情况下,可以使用依赖项注入容器来初始化服务,以避免 NullReferenceException。因此,这意味着您无需担心检查 null,只需从控制器调用服务,就好像它们将始终作为单例或原型可用(并初始化)一样。

public class MyController
{
    private ServiceA serviceA;
    private ServiceB serviceB;

    public MyController(ServiceA serviceA, ServiceB serviceB)
    {
        this.serviceA = serviceA;
        this.serviceB = serviceB;
    }

    public void MyMethod()
    {
        // We don't need to check null because the dependency injection container 
        // injects it, provided you took care of bootstrapping it.
        var someObject = serviceA.DoThis();
    }
}

评论

6赞 John Saunders 3/7/2014
-1:这仅处理单个场景 - 未初始化的依赖项。这是 NullReferenceException 的少数方案。大多数情况只是对对象工作原理的简单误解。接下来最常见的是开发人员假定对象将自动初始化的其他情况。
0赞 John Saunders 3/7/2014
为了避免 NullReferenceException,通常不使用依赖项注入。我不相信你在这里找到了一个一般的场景。无论如何,如果你把你的答案编辑得更像 stackoverflow.com/a/15232518/76337 风格,那么我会删除反对票。
64赞 4 revs, 3 users 81%Jeppe Stig Nielsen #15

西蒙·穆里尔(Simon Mourier)举了这个例子

object o = null;
DateTime d = (DateTime)o;  // NullReferenceException

其中,从(或其中一个类或 或 或从接口类型)值类型(除 )的拆箱转换(强制转换)本身会给出 .objectSystem.ValueTypeSystem.EnumNullable<>NullReferenceException

在另一个方向上, 等于 的 到 引用类型的装箱转换可以给出一个引用,然后可以导致 .典型的例子是:Nullable<>HasValuefalsenullNullReferenceException

DateTime? d = null;
var s = d.ToString();  // OK, no exception (no boxing), returns ""
var t = d.GetType();   // Bang! d is boxed, NullReferenceException

有时拳击以另一种方式发生。例如,使用此非泛型扩展方法:

public static void MyExtension(this object x)
{
  x.ToString();
}

以下代码将有问题:

DateTime? d = null;
d.MyExtension();  // Leads to boxing, NullReferenceException occurs inside the body of the called method, not here.

出现这些情况的原因是运行时在装箱实例时使用的特殊规则。Nullable<>

330赞 12 revs, 7 users 76%Plutonix #16

NullReference 异常 — Visual Basic

Visual BasicC# 中的没有什么不同。毕竟,它们都报告了它们在它们都使用的 .NET Framework 中定义的相同异常。Visual Basic 特有的原因很少见(可能只有一个)。NullReference Exception

此答案将使用 Visual Basic 术语、语法和上下文。所使用的示例来自过去的大量 Stack Overflow 问题。这是通过使用帖子中经常看到的各种情况来最大限度地提高相关性。还为那些可能需要它的人提供了更多的解释。这里很可能列出了一个与您的示例类似的示例。

注意:

  1. 这是基于概念的:没有代码可供您粘贴到项目中。它旨在帮助您了解导致 (NRE) 的原因、如何找到它、如何修复它以及如何避免它。NRE 可以通过多种方式引起,因此这不太可能是您唯一的遭遇。NullReferenceException
  2. 这些示例(来自 Stack Overflow 帖子)并不总是首先显示做某事的最佳方法。
  3. 通常,使用最简单的补救措施。

基本含义

消息“对象未设置为对象的实例”表示您正在尝试使用尚未初始化的对象。这归结为以下之一:

  • 您的代码声明了一个对象变量,但它没有初始化它(创建一个实例或“实例化”它)
  • 您的代码假定会初始化对象的东西没有
  • 其他代码可能过早地使仍在使用的对象失效

寻找原因

由于问题是一个对象引用,答案是检查它们以找出哪一个。然后确定它未初始化的原因。将鼠标悬停在各种变量上,Visual Studio (VS) 将显示它们的值 - 罪魁祸首将是 。NothingNothing

IDE调试显示

您还应该从相关代码中删除任何 Try/Catch 块,尤其是那些 Catch 块中没有任何内容的代码块。这将导致您的代码在尝试使用对象时崩溃。这是您想要的,因为它将识别问题的确切位置,并允许您识别导致问题的对象。Nothing

A 在显示的 Catch 中将无济于事。这种方法还会导致非常糟糕的 Stack Overflow 问题,因为您无法描述实际的异常、涉及的对象,甚至无法描述发生异常的代码行。MsgBoxError while...

还可以使用(调试 -> Windows -> 局部变量)来检查对象。Locals Window

一旦您知道问题是什么以及问题在哪里,通常很容易解决,而且比发布新问题更快。

另请参阅:

示例和补救措施

类对象 / 创建实例

Dim reg As CashRegister
...
TextBox1.Text = reg.Amount         ' NRE

问题是不会创建 CashRegister 对象;它只声明一个名为该类型的变量。声明对象变量和创建实例是两回事。Dimreg

补救

在声明实例时,运算符通常可用于创建实例:New

Dim reg As New CashRegister        ' [New] creates instance, invokes the constructor

' Longer, more explicit form:
Dim reg As CashRegister = New CashRegister

当只适合稍后创建实例时:

Private reg As CashRegister         ' Declare
  ...
reg = New CashRegister()            ' Create instance

注意:不要在过程中再次使用,包括构造函数 ():DimSub New

Private reg As CashRegister
'...

Public Sub New()
   '...
   Dim reg As New CashRegister
End Sub

这将创建一个局部变量,该变量仅存在于该上下文 (sub) 中。您将在其他任何地方使用的具有模块级别的变量仍然存在。regregScopeNothing

缺少 New 运算符是 NullReference 异常的 #1 原因,在审查的 Stack Overflow 问题中出现。

Visual Basic 尝试使用 New 反复使该过程清晰:使用 New 运算符创建一个对象并调用 Sub New(构造函数),其中对象可以执行任何其他初始化。

需要明确的是,(或 ) 只声明一个变量及其 .变量的作用域 - 无论它存在于整个模块/类中还是过程的局部变量 - 都由它的声明位置决定。 定义访问级别,而不是范围DimPrivateTypePrivate | Friend | Public

有关详细信息,请参阅:


阵 列

数组也必须实例化:

Private arr as String()

此数组仅已声明,未创建。有几种方法可以初始化数组:

Private arr as String() = New String(10){}
' or
Private arr() As String = New String(10){}

' For a local array (in a procedure) and using 'Option Infer':
Dim arr = New String(10) {}

注意:从 VS 2010 开始,当使用文字 和 初始化局部数组时,和元素是可选的:Option InferAs <Type>New

Dim myDbl As Double() = {1.5, 2, 9.9, 18, 3.14}
Dim myDbl = New Double() {1.5, 2, 9.9, 18, 3.14}
Dim myDbl() = {1.5, 2, 9.9, 18, 3.14}

数据类型和数组大小是从分配的数据中推断出来的。类/模块级别声明仍然需要:As <Type>Option Strict

Private myDoubles As Double() = {1.5, 2, 9.9, 18, 3.14}

示例:类对象的数组

Dim arrFoo(5) As Foo

For i As Integer = 0 To arrFoo.Count - 1
   arrFoo(i).Bar = i * 10       ' Exception
Next

数组已创建,但其中的对象尚未创建。Foo

补救

For i As Integer = 0 To arrFoo.Count - 1
    arrFoo(i) = New Foo()         ' Create Foo instance
    arrFoo(i).Bar = i * 10
Next

使用 will 会使得没有有效对象的元素变得非常困难:List(Of T)

Dim FooList As New List(Of Foo)     ' List created, but it is empty
Dim f As Foo                        ' Temporary variable for the loop

For i As Integer = 0 To 5
    f = New Foo()                    ' Foo instance created
    f.Bar =  i * 10
    FooList.Add(f)                   ' Foo object added to list
Next

有关详细信息,请参阅:


列表和集合

还必须实例化或创建 .NET 集合(其中有许多种类 - 列表、字典等)。

Private myList As List(Of String)
..
myList.Add("ziggy")           ' NullReference

出于同样的原因,您会收到相同的异常 - 仅声明,但未创建实例。补救措施是一样的:myList

myList = New List(Of String)

' Or create an instance when declared:
Private myList As New List(Of String)

一个常见的疏忽是使用集合的类:Type

Public Class Foo
    Private barList As List(Of Bar)

    Friend Function BarCount As Integer
        Return barList.Count
    End Function

    Friend Sub AddItem(newBar As Bar)
        If barList.Contains(newBar) = False Then
            barList.Add(newBar)
        End If
    End Function

任一过程都会导致 NRE,因为仅声明,未实例化。创建 的实例不会同时创建内部 的实例。可能是在构造函数中执行此操作的意图:barListFoobarList

Public Sub New         ' Constructor
    ' Stuff to do when a new Foo is created...
    barList = New List(Of Bar)
End Sub

和以前一样,这是不正确的:

Public Sub New()
    ' Creates another barList local to this procedure
     Dim barList As New List(Of Bar)
End Sub

有关更多信息,请参见 List(Of T)


数据提供程序对象

使用数据库为 NullReference 提供了许多机会,因为可以同时使用多个对象(、、、...)。注意:无论您使用哪个数据提供程序(MySQL、SQL Server、OleDB 等),概念都是相同的。CommandConnectionTransactionDatasetDataTableDataRows

示例 1

Dim da As OleDbDataAdapter
Dim ds As DataSet
Dim MaxRows As Integer

con.Open()
Dim sql = "SELECT * FROM tblfoobar_List"
da = New OleDbDataAdapter(sql, con)
da.Fill(ds, "foobar")
con.Close()

MaxRows = ds.Tables("foobar").Rows.Count      ' Error

和以前一样,声明了 Dataset 对象,但从未创建实例。将填充现有的 ,而不是创建一个。在本例中,由于是局部变量,因此 IDE 会警告您可能会发生以下情况:dsDataAdapterDataSetds

img

当声明为模块/类级变量时,编译器无法知道该对象是否由上游过程创建。不要忽视警告。con

补救

Dim ds As New DataSet

示例 2

ds = New DataSet
da = New OleDBDataAdapter(sql, con)
da.Fill(ds, "Employees")

txtID.Text = ds.Tables("Employee").Rows(0).Item(1)
txtID.Name = ds.Tables("Employee").Rows(0).Item(2)

错别字在这里是一个问题:vs .没有创建命名的“员工”,因此尝试访问它的结果。另一个潜在的问题是假设当 SQL 包含 WHERE 子句时,可能会有 which 可能并非如此。EmployeesEmployeeDataTableNullReferenceExceptionItems

补救

由于这使用一个表,因此使用 将避免拼写错误。检查还有助于:Tables(0)Rows.Count

If ds.Tables(0).Rows.Count > 0 Then
    txtID.Text = ds.Tables(0).Rows(0).Item(1)
    txtID.Name = ds.Tables(0).Rows(0).Item(2)
End If

Fill是一个返回受影响数量的函数,也可以进行测试:Rows

If da.Fill(ds, "Employees") > 0 Then...

示例 3

Dim da As New OleDb.OleDbDataAdapter("SELECT TICKET.TICKET_NO,
        TICKET.CUSTOMER_ID, ... FROM TICKET_RESERVATION AS TICKET INNER JOIN
        FLIGHT_DETAILS AS FLIGHT ... WHERE [TICKET.TICKET_NO]= ...", con)
Dim ds As New DataSet
da.Fill(ds)

If ds.Tables("TICKET_RESERVATION").Rows.Count > 0 Then

将提供如上例所示,但它不会解析 SQL 或数据库表中的名称。因此,引用一个不存在的表。DataAdapterTableNamesds.Tables("TICKET_RESERVATION")

补救措施是一样的,按索引引用表格:

If ds.Tables(0).Rows.Count > 0 Then

另请参见 DataTable 类


对象路径/嵌套

If myFoo.Bar.Items IsNot Nothing Then
   ...

代码只是在两者之间进行测试,也可能是 Nothing。补救措施是一次测试一个对象的整个链或路径:ItemsmyFooBar

If (myFoo IsNot Nothing) AndAlso
    (myFoo.Bar IsNot Nothing) AndAlso
    (myFoo.Bar.Items IsNot Nothing) Then
    ....

AndAlso很重要。一旦遇到第一个条件,将不会执行后续测试。这允许代码一次安全地“钻取”到对象一个“级别”,仅在确定(以及是否)有效之后进行评估。在对复杂对象进行编码时,对象链或路径可能会很长:FalsemyFoo.BarmyFoo

myBase.myNodes(3).Layer.SubLayer.Foo.Files.Add("somefilename")

不可能引用对象的“下游”任何内容。这也适用于控件:null

myWebBrowser.Document.GetElementById("formfld1").InnerText = "some value"

在这里,或者可能是 Nothing,或者元素可能不存在。myWebBrowserDocumentformfld1


UI 控件

Dim cmd5 As New SqlCommand("select Cartons, Pieces, Foobar " _
     & "FROM Invoice where invoice_no = '" & _
     Me.ComboBox5.SelectedItem.ToString.Trim & "' And category = '" & _
     Me.ListBox1.SelectedItem.ToString.Trim & "' And item_name = '" & _
     Me.ComboBox2.SelectedValue.ToString.Trim & "' And expiry_date = '" & _
     Me.expiry.Text & "'", con)

除其他事项外,此代码不会预料到用户可能未在一个或多个 UI 控件中选择某些内容。 很可能是 ,因此将导致 NRE。ListBox1.SelectedItemNothingListBox1.SelectedItem.ToString

补救

在使用数据之前验证数据(也请使用 和 SQL 参数):Option Strict

Dim expiry As DateTime         ' for text date validation
If (ComboBox5.SelectedItems.Count > 0) AndAlso
    (ListBox1.SelectedItems.Count > 0) AndAlso
    (ComboBox2.SelectedItems.Count > 0) AndAlso
    (DateTime.TryParse(expiry.Text, expiry) Then

    '... do stuff
Else
    MessageBox.Show(...error message...)
End If

或者,您可以使用(ComboBox5.SelectedItem IsNot Nothing) AndAlso...


Visual Basic 窗体

Public Class Form1

    Private NameBoxes = New TextBox(5) {Controls("TextBox1"), _
                   Controls("TextBox2"), Controls("TextBox3"), _
                   Controls("TextBox4"), Controls("TextBox5"), _
                   Controls("TextBox6")}

    ' same thing in a different format:
    Private boxList As New List(Of TextBox) From {TextBox1, TextBox2, TextBox3 ...}

    ' Immediate NRE:
    Private somevar As String = Me.Controls("TextBox1").Text

这是获得 NRE 的一种相当常见的方法。在 C# 中,根据编码方式,IDE 将报告当前上下文中不存在的内容,或者“无法引用非静态成员”。因此,在某种程度上,这是仅 VB 的情况。它也很复杂,因为它可能导致故障级联。Controls

数组和集合不能以这种方式初始化。此初始化代码将在构造函数创建 或 之前运行。因此:FormControls

  • “列表”和“集合”将仅为空
  • 数组将包含 Nothing 的五个元素
  • 分配将导致立即 NRE,因为 Nothing 没有属性somevar.Text

稍后引用数组元素将导致 NRE。如果在 中执行此操作,则由于奇怪的错误,IDE 可能不会在发生异常时报告异常。稍后,当您的代码尝试使用数组时,将弹出异常。这篇文章详细介绍了这个“无声的例外”。就我们的目的而言,关键是,当在创建表单(或事件)时发生灾难性事件时,异常可能未被报告,代码将退出过程并仅显示表单。Form_LoadSub NewForm Load

由于 or 事件中没有其他代码会在 NRE 之后运行,因此许多其他内容可以保持未初始化状态。Sub NewForm Load

Sub Form_Load(..._
   '...
   Dim name As String = NameBoxes(2).Text        ' NRE
   ' ...
   ' More code (which will likely not be executed)
   ' ...
End Sub

请注意,这适用于任何和所有控件和组件引用,使它们在以下情况下是非法的:

Public Class Form1

    Private myFiles() As String = Me.OpenFileDialog1.FileName & ...
    Private dbcon As String = OpenFileDialog1.FileName & ";Jet Oledb..."
    Private studentName As String = TextBox13.Text

部分补救措施

奇怪的是,VB 没有提供警告,但补救措施是在表单级别声明容器,但在控件确实存在时在表单加载事件处理程序中初始化它们。只要您的代码在调用之后,就可以完成此操作:Sub NewInitializeComponent

' Module level declaration
Private NameBoxes as TextBox()
Private studentName As String

' Form Load, Form Shown or Sub New:
'
' Using the OP's approach (illegal using OPTION STRICT)
NameBoxes = New TextBox() {Me.Controls("TextBox1"), Me.Controls("TestBox2"), ...)
studentName = TextBox32.Text           ' For simple control references

数组代码可能还没有走出困境。容器控件中的任何控件(如 或 )都不会在 ;它们将位于该 Panel 或 GroupBox 的 Controls 集合中。当控件名称拼写错误时,也不会返回控件 ()。在这种情况下,将再次存储在这些数组元素中,并且当您尝试引用它时将产生 NRE。GroupBoxPanelMe.Controls"TeStBox2"Nothing

现在你知道你在寻找什么,这些应该很容易找到。VS 向你展示了你的方式的错误

“Button2”驻留在Panel

补救

不要使用窗体的集合按名称间接引用,而是使用控件引用:Controls

' Declaration
Private NameBoxes As TextBox()

' Initialization -  simple and easy to read, hard to botch:
NameBoxes = New TextBox() {TextBox1, TextBox2, ...)

' Initialize a List
NamesList = New List(Of TextBox)({TextBox1, TextBox2, TextBox3...})
' or
NamesList = New List(Of TextBox)
NamesList.AddRange({TextBox1, TextBox2, TextBox3...})

函数不返回任何内容

Private bars As New List(Of Bars)        ' Declared and created

Public Function BarList() As List(Of Bars)
    bars.Clear
    If someCondition Then
        For n As Integer = 0 to someValue
            bars.Add(GetBar(n))
        Next n
    Else
        Exit Function
    End If

    Return bars
End Function

在这种情况下,IDE 会警告您“并非所有路径都返回值,并且可能会导致 NullReferenceException”。您可以通过替换为 来禁止显示警告,但这并不能解决问题。任何尝试在以下时间使用返回值将导致 NRE:Exit FunctionReturn NothingsomeCondition = False

bList = myFoo.BarList()
For Each b As Bar in bList      ' EXCEPTION
      ...

补救

将函数中的 替换为 。返回值与返回 不同。如果返回的对象有可能是 ,请在使用前进行测试:Exit FunctionReturn bListListNothingNothing

 bList = myFoo.BarList()
 If bList IsNot Nothing Then...

Try/Catch 实施不当

一个实施不当的 Try/Catch 可能会隐藏问题所在并导致新的问题:

Dim dr As SqlDataReader
Try
    Dim lnk As LinkButton = TryCast(sender, LinkButton)
    Dim gr As GridViewRow = DirectCast(lnk.NamingContainer, GridViewRow)
    Dim eid As String = GridView1.DataKeys(gr.RowIndex).Value.ToString()
    ViewState("username") = eid
    sqlQry = "select FirstName, Surname, DepartmentName, ExtensionName, jobTitle,
             Pager, mailaddress, from employees1 where username='" & eid & "'"
    If connection.State <> ConnectionState.Open Then
        connection.Open()
    End If
    command = New SqlCommand(sqlQry, connection)

    'More code fooing and barring

    dr = command.ExecuteReader()
    If dr.Read() Then
        lblFirstName.Text = Convert.ToString(dr("FirstName"))
        ...
    End If
    mpe.Show()
Catch

Finally
    command.Dispose()
    dr.Close()             ' <-- NRE
    connection.Close()
End Try

这是未按预期创建对象的情况,但也演示了空 .Catch

SQL 中有一个额外的逗号(在“mailaddress”之后),这会导致 处出现异常。之后,不执行任何操作,尝试执行清理,但由于不能为空对象,因此会出现全新的结果。.ExecuteReaderCatchFinallyCloseDataReaderNullReferenceException

空荡荡的街区是魔鬼的游乐场。这个 OP 很困惑为什么他会在街区中得到 NRE。在其他情况下,空的可能会导致下游的其他东西失控,并导致您花时间在错误的地方查看错误的东西。(上述“无声例外”提供了相同的娱乐价值。CatchFinallyCatch

补救

不要使用空的 Try/Catch 块 - 让代码崩溃,这样您就可以 a) 确定原因 b) 确定位置和 c) 应用适当的补救措施。Try/Catch 块并非旨在向唯一有资格修复异常的人(开发人员)隐藏异常。


DBNull 与 Nothing 不同

For Each row As DataGridViewRow In dgvPlanning.Rows
    If Not IsDBNull(row.Cells(0).Value) Then
        ...

该函数用于测试是否等于: 来自 MSDN:IsDBNullSystem.DBNull

System.DBNull 值指示 Object 表示缺少或不存在的数据。DBNull 与 Nothing 不同,后者表示尚未初始化变量。

补救

If row.Cells(0) IsNot Nothing Then ...

和以前一样,您可以测试“无”,然后测试特定值:

If (row.Cells(0) IsNot Nothing) AndAlso (IsDBNull(row.Cells(0).Value) = False) Then

示例 2

Dim getFoo = (From f In dbContext.FooBars
               Where f.something = something
               Select f).FirstOrDefault

If Not IsDBNull(getFoo) Then
    If IsDBNull(getFoo.user_id) Then
        txtFirst.Text = getFoo.first_name
    Else
       ...

FirstOrDefault返回第一项或默认值,该值用于引用类型,从不:NothingDBNull

If getFoo IsNot Nothing Then...

控制

Dim chk As CheckBox

chk = CType(Me.Controls(chkName), CheckBox)
If chk.Checked Then
    Return chk
End If

如果找不到 (或存在于 中),则 将为 Nothing,并且尝试引用任何属性将导致异常。CheckBoxchkNameGroupBoxchk

补救

If (chk IsNot Nothing) AndAlso (chk.Checked) Then ...

The DataGridView

DGV 有一些定期出现的怪癖:

dgvBooks.DataSource = loan.Books
dgvBooks.Columns("ISBN").Visible = True       ' NullReferenceException
dgvBooks.Columns("Title").DefaultCellStyle.Format = "C"
dgvBooks.Columns("Author").DefaultCellStyle.Format = "C"
dgvBooks.Columns("Price").DefaultCellStyle.Format = "C"

如果 has ,它将创建列,但不会命名它们,因此上面的代码在按名称引用它们时会失败。dgvBooksAutoGenerateColumns = True

补救

手动命名列,或按索引引用:

dgvBooks.Columns(0).Visible = True

示例 2 — 当心 NewRow

xlWorkSheet = xlWorkBook.Sheets("sheet1")

For i = 0 To myDGV.RowCount - 1
    For j = 0 To myDGV.ColumnCount - 1
        For k As Integer = 1 To myDGV.Columns.Count
            xlWorkSheet.Cells(1, k) = myDGV.Columns(k - 1).HeaderText
            xlWorkSheet.Cells(i + 2, j + 1) = myDGV(j, i).Value.ToString()
        Next
    Next
Next

当您的 has as(默认值)时,底部的空白/新行中将全部包含 .大多数使用内容的尝试(例如,)将导致 NRE。DataGridViewAllowUserToAddRowsTrueCellsNothingToString

补救

使用循环并测试属性以确定它是否是最后一行。无论是否属实,这都有效:For/EachIsNewRowAllowUserToAddRows

For Each r As DataGridViewRow in myDGV.Rows
    If r.IsNewRow = False Then
         ' ok to use this row

如果确实使用循环,请修改行计数或使用 when 为 true。For nExit ForIsNewRow


My.Settings (StringCollection)

在某些情况下,尝试使用属于 a 的项可能会导致首次使用时出现 NullReference。解决方案是一样的,但不是那么明显。考虑:My.SettingsStringCollection

My.Settings.FooBars.Add("ziggy")         ' foobars is a string collection

由于 VB 正在为您管理设置,因此可以合理地期望它初始化集合。它会,但前提是您之前已将初始条目添加到集合中(在“设置”编辑器中)。由于集合(显然)在添加项目时已初始化,因此当“设置”编辑器中没有要添加的项目时,该集合将保持不变。Nothing

补救

如果需要,在窗体的事件处理程序中初始化设置集合:Load

If My.Settings.FooBars Is Nothing Then
    My.Settings.FooBars = New System.Collections.Specialized.StringCollection
End If

通常,只需在应用程序首次运行时初始化集合。另一种补救措施是在“项目”->“设置”|”FooBars,保存项目,然后删除假值。Settings


要点

您可能忘记了操作员。New

您认为可以完美地将初始化对象返回到代码中的东西,但事实并非如此。

不要忽略编译器警告(ever)并使用(always)。Option Strict On


MSDN NullReference 异常

42赞 5 revsNick L. #17

关于“我该怎么办”的问题,可以有很多答案。

在开发过程中防止此类错误情况的一种更“正式”的方法是在代码中应用合同设计。这意味着在开发过程中,您需要在系统上设置类不变量,和/甚至函数/方法前置条件和后置条件

简而言之,类不变量确保类中存在一些约束,这些约束在正常使用中不会被违反(因此,类不会处于不一致的状态)。前提条件意味着作为函数/方法输入的数据必须遵循某些约束集并且绝不违反它们,而后置条件意味着函数/方法输出必须再次遵循设置的约束,而不会违反它们。 在执行无错误的程序期间,绝不应违反合同条件,因此在实践中,在调试模式下检查合同设计,同时在版本中禁用,以最大限度地提高开发的系统性能。

这样,可以避免因违反约束集而导致的情况。例如,如果您在类中使用对象属性,然后尝试调用其方法之一并具有 null 值,则这将导致:NullReferenceExceptionXXNullReferenceException

public X { get; set; }

public void InvokeX()
{
    X.DoSomething(); // if X value is null, you will get a NullReferenceException
}

但是,如果将“属性 X 绝不能具有 null 值”设置为方法前提条件,则可以防止出现前面描述的情况:

//Using code contracts:
[ContractInvariantMethod]
protected void ObjectInvariant() 
{
    Contract.Invariant(X != null);
    //...
}

因此,存在 .NET 应用程序的代码协定项目。

或者,可以使用断言来应用合同设计。

更新:值得一提的是,该术语是由 Bertrand Meyer 在设计 Eiffel 编程语言时创造的。

评论

2赞 Nick Louloudakis 12/26/2014
我想补充一下,因为没有人提到这一点,而且就它作为一种方法存在而言,我的目的是丰富这个话题。
2赞 VoteCoffee 1/8/2015
我认为这是该主题的一个有价值的补充,因为这是一个浏览量很大的线程。我以前听说过代码契约,这是一个很好的提醒,要考虑使用它们。
35赞 4 revs, 2 users 91%Travis Heeter #18

TL;DR:尝试使用而不是Html.PartialRenderpage


当我试图通过向视图发送模型来渲染视图中的视图时,我得到了这样的结果:Object reference not set to an instance of an object

@{
    MyEntity M = new MyEntity();
}
@RenderPage("_MyOtherView.cshtml", M); // error in _MyOtherView, the Model was Null

调试显示模型在 MyOtherView 中为 Null。直到我把它改成:

@{
    MyEntity M = new MyEntity();
}
@Html.Partial("_MyOtherView.cshtml", M);

它奏效了。

此外,我不必开始的原因是,如果 Visual Studio 位于不同构造的循环中,有时会在它下面抛出看起来错误的波浪线,即使它不是真正的错误:Html.PartialHtml.Partialforeach

@inherits System.Web.Mvc.WebViewPage
@{
    ViewBag.Title = "Entity Index";
    List<MyEntity> MyEntities = new List<MyEntity>();
    MyEntities.Add(new MyEntity());
    MyEntities.Add(new MyEntity());
    MyEntities.Add(new MyEntity());
}
<div>
    @{
        foreach(var M in MyEntities)
        {
            // Squiggly lines below. Hovering says: cannot convert method group 'partial' to non-delegate type Object, did you intend to envoke the Method?
            @Html.Partial("MyOtherView.cshtml");
        }
    }
</div>

但是我能够运行该应用程序,而这个“错误”没有问题。我能够通过将循环的结构更改为如下所示来消除错误:foreach

@foreach(var M in MyEntities){
    ...
}

虽然我有一种感觉,这是因为 Visual Studio 误读了 & 符号和括号。

评论

0赞 John Saunders 7/24/2015
你想要的,不是Html.Partial@Html.Partial
0赞 John Saunders 7/24/2015
另外,请说明哪一行抛出了异常,以及原因。
0赞 Travis Heeter 7/27/2015
该错误发生在 MyOtherView.cshtml 中,我未在此处包含该错误,因为模型未正确发送(确实如此),因此我知道错误出在我发送模型的方式上。Null
6赞 3 revs, 2 users 70%Akash Chowdary #19

好吧,简单来说:

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

那么如何解决这个问题呢:

  1. 调试并让调试器中断...它会直接带你到被破坏的变量......现在你的任务是简单地解决这个问题。在适当的位置使用 new 关键字。

  2. 如果它是在某些数据库命令上导致的,因为对象不存在,那么你需要做的就是做一个空检查并处理它:

    if (i == null) {
        // Handle this
    }
    
  3. 最难的..如果 GC 已经收集了对象...如果您尝试使用字符串查找对象,通常会发生这种情况......也就是说,通过对象的名称找到它,那么 GC 可能已经清理了它......这很难找到,会成为一个很大的问题......解决此问题的更好方法是在开发过程中在必要时进行空检查。这将为您节省大量时间。

通过按名称查找,我的意思是一些框架允许您使用字符串进行 FIndObjects,代码可能如下所示: FindObject(“ObjectName”);

评论

3赞 John Saunders 12/24/2015
如果您有对某个对象的引用,则 GC 永远不会清理它
2赞 Akash Gutha 12/24/2015
如果你使用像 FindObject(“Name of Object”) 这样的东西,GC 就不可能事先知道你要引用该对象。这就是试图解释的东西..这些发生在运行时
2赞 Akash Gutha 12/24/2015
有一些框架在 C# 中提供此功能,例如 Unity 。这个问题与BCl无关。 在批评之前搜索互联网,有很多像它们这样的功能,为了您的信息,我什至每天都使用它。现在请告诉我答案怎么没有任何意义。
2赞 Akash Gutha 12/24/2015
docs.unity3d.com/ScriptReference/......检查链接并更正自己的 Mr.Expert :p
0赞 John Saunders 5/26/2016
我在您的链接中看到的示例将 GameObject.Find 的结果分配给成员字段。这是一个引用,在收集包含对象之前,GC 不会收集它。
24赞 4 revs, 2 users 98%Luis Perez #20

你能做些什么?

这里有很多很好的答案,解释了什么是空引用以及如何调试它。但是,关于如何预防这个问题或至少使其更容易被发现,却很少。

检查参数

例如,方法可以检查不同的参数以查看它们是否为 null 并抛出一个 ,这显然是为此目的而创建的异常。ArgumentNullException

偶数的构造函数将参数的名称和消息作为参数,以便您可以准确地告诉开发人员问题所在。ArgumentNullException

public void DoSomething(MyObject obj) {
    if(obj == null) 
    {
        throw new ArgumentNullException("obj", "Need a reference to obj.");
    }
}

使用工具

还有几个库可以提供帮助。例如,“Resharper”可以在您编写代码时为您提供警告,尤其是在您使用其属性时:NotNullAttribute

有“Microsoft 代码协定”,您可以在其中使用语法,例如为您提供运行时和编译检查:引入代码协定Contract.Requires(obj != null)

还有“PostSharp”,它允许你使用这样的属性:

public void DoSometing([NotNull] obj)

通过这样做并使 PostSharp 成为构建过程的一部分,将在运行时检查是否为 null。请参见: PostSharp 空检查obj

纯代码解决方案

或者,您始终可以使用普通的旧代码编写自己的方法。例如,下面是一个可用于捕获 null 引用的结构。它以以下相同概念为模型:Nullable<T>

[System.Diagnostics.DebuggerNonUserCode]
public struct NotNull<T> where T: class
{
    private T _value;

    public T Value
    {
        get
        {
            if (_value == null)
            {
                throw new Exception("null value not allowed");
            }

            return _value;
        }
        set
        {
            if (value == null)
            {
                throw new Exception("null value not allowed.");
            }

            _value = value;
        }
    }

    public static implicit operator T(NotNull<T> notNullValue)
    {
        return notNullValue.Value;
    }

    public static implicit operator NotNull<T>(T value)
    {
        return new NotNull<T> { Value = value };
    }
}

您的使用方式与使用的方式非常相似,只是其目标是实现完全相反的 - 不允许 。以下是一些示例:Nullable<T>null

NotNull<Person> person = null; // throws exception
NotNull<Person> person = new Person(); // OK
NotNull<Person> person = GetPerson(); // throws exception if GetPerson() returns null

NotNull<T>隐式转换为和从中转换,因此您几乎可以在需要的任何地方使用它。例如,您可以将对象传递给采用以下方法的方法:TPersonNotNull<Person>

Person person = new Person { Name = "John" };
WriteName(person);

public static void WriteName(NotNull<Person> person)
{
    Console.WriteLine(person.Value.Name);
}

如上所述,与 nullable 一样,您将通过属性访问基础值。或者,您可以使用显式或隐式强制转换,您可以看到下面返回值的示例:Value

Person person = GetPerson();

public static NotNull<Person> GetPerson()
{
    return new Person { Name = "John" };
}

或者,您甚至可以在方法返回时使用它(在本例中),方法是进行强制转换。例如,以下代码与上面的代码类似:TPerson

Person person = (NotNull<Person>)GetPerson();

public static Person GetPerson()
{
    return new Person { Name = "John" };
}

与扩展相结合

结合扩展方法,您可以涵盖更多情况。下面是扩展方法的示例:NotNull<T>

[System.Diagnostics.DebuggerNonUserCode]
public static class NotNullExtension
{
    public static T NotNull<T>(this T @this) where T: class
    {
        if (@this == null)
        {
            throw new Exception("null value not allowed");
        }

        return @this;
    }
}

下面是一个如何使用它的示例:

var person = GetPerson().NotNull();

GitHub的

为了供您参考,我在 GitHub 上提供了上面的代码,您可以在以下位置找到它:

https://github.com/luisperezphd/NotNull

相关语言功能

C# 6.0 引入了“null-conditional 运算符”,对此有一点帮助。使用此功能,您可以引用嵌套对象,如果其中任何一个是整个表达式,则返回 。nullnull

这样可以减少在某些情况下必须执行的 null 检查次数。语法是在每个点之前加一个问号。以以下代码为例:

var address = country?.State?.County?.City;

想象一下,这是一个类型的对象,它有一个属性,称为等等。如果 , , 或 则为 nulladdressnull'。countryCountryStatecountryStateCountyCitynulladdress will be. Therefore you only have to check whetheris

这是一个很棒的功能,但它为您提供的信息较少。这并不能明确 4 个中哪一个是空的。

像 Nullable 一样内置?

C#有一个很好的简写,你可以通过在类型后面加上一个问号来使一些东西可以为空,就像这样。Nullable<T>int?

如果 C# 有类似上面的结构并且有类似的速记,也许是感叹号 (!),这样你就可以写出类似的东西,那就太好了:.NotNull<T>public void WriteName(Person! person)

评论

2赞 John Saunders 3/7/2016
从不引发 NullReferenceException
2赞 John Saunders 3/7/2016
NullReferenceException 应由 CLR 引发。这意味着发生了对 null 的引用。这并不意味着会发生对 null 的引用,除非您先巧妙地检查了。
0赞 Darrel Lee 5/3/2016
对于这样一个基本问题,这是一个很好的答案。当你的代码失败时,情况并没有那么糟糕。当它来自您所依赖的一些商业第三方库的深处时,这很可怕,并且客户支持一直坚持认为必须是您的代码导致了问题。而且你不完全确定它不是,整个项目都停止了。实际上,我认为这可能会为我的墓碑制作一个合适的墓志铭:“对象引用未设置为对象的实例。
6赞 Nick #21

NullReferenceException 或未设置为对象实例的对象引用 当您尝试使用的类的对象未实例化时。 例如:

假设您有一个名为 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。

10赞 2 revs, 2 users 88%Jaimin Dave #22

错误行“对象引用未设置为对象的实例”表示您尚未将实例对象分配给对象引用,并且您仍在访问该对象的属性/方法。

例如:假设你有一个名为 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;
     }
}
11赞 4 revs, 4 users 54%jazzcat #23

有趣的是,本页的答案都没有提到两种极端情况:

边缘案例 #1:并发访问字典

.NET 中的泛型字典不是线程安全的,当您尝试从两个并发线程访问密钥时,它们有时可能会引发甚至(更频繁)的 a。在这种情况下,例外非常具有误导性。NullReferenceKeyNotFoundException

边缘情况 #2:不安全的代码

如果 a 是由代码抛出的,您可以查看指针变量,并检查它们是否存在其他问题。这是一回事(“空指针异常”),但在不安全的代码中,变量经常被强制转换为值类型/数组等,你把头撞在墙上,想知道值类型如何抛出这个异常。NullReferenceExceptionunsafeIntPtr.Zero

(顺便说一句,除非您需要,否则不使用不安全代码的另一个原因。

边缘情况 #3:具有与主监视器具有不同 DPI 设置的辅助监视器的 Visual Studio 多监视器设置

此边缘情况特定于软件,与 Visual Studio 2019 IDE(可能更早的版本)有关。

重现该问题的方法:将任何组件从“工具箱”拖动到非主监视器上的 Windows 窗体上,其 DPI 设置与主监视器不同,并且会出现一个弹出窗口,其中包含“对象引用未设置为对象的实例”。根据这个线程,这个问题已经存在了很长一段时间,在撰写本文时仍未得到解决。

评论

5赞 John Saunders 3/24/2017
您的字典示例不是边缘情况。如果对象不是线程安全的,则从多个线程使用它会产生随机结果。您的不安全代码示例在哪些方面有所不同?null
-7赞 4 revs, 3 users 89%Wasim #24

这基本上是一个 Null 引用异常。正如Microsoft所说——

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

那是什么意思?

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

“嘿,等等,那个成员没有值,所以它不能执行你交给它的任务。”

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

如果我们使用 Value 类型成员,则不会发生 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 未赋值,这意味着我们的变量具有 null 值或什么都没有。上述代码的编译器会引发一个错误,即变量 obj 未赋值,这意味着我们的变量具有 null 值或什么都没有。

为什么会这样?

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

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

如何避免?

有多种方法和方法可以避免这个著名的例外:

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

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

  3. 空运算符:空合并运算符和空条件运算符也可以在为对象、变量、属性和字段设置值时方便使用。

  4. 调试器:对于开发人员来说,我们拥有调试这一重要武器。如果我们在开发过程中遇到 NullReferenceException,我们可以使用调试器来获取异常的来源。

  5. 内置方法:GetValueOrDefault()、IsNullOrWhiteSpace() 和 IsNullorEmpty() 等系统方法检查空值,如果存在空值,则分配默认值。

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

希望这也有所帮助!

3赞 3 revs, 3 users 63%CausticLasagne #25

从字面上看,修复 NullReferenceExeption 的最简单方法有两种方法。

例如,如果您有一个附加了脚本的游戏对象和一个名为 rb (rigidbody) 的变量,则当您启动游戏时,此变量将以 null 开头。 这就是获得 NullReferenceExeption 的原因,因为计算机没有在该变量中存储数据。

我将使用 RigidBody 变量作为示例。 实际上,我们可以通过以下几种方式轻松添加数据:

  1. 使用 AddComponent > Physics > Rigidbody 将 RigidBody 添加到对象 然后进入你的脚本并键入 This line of code code work best under your or functions.rb = GetComponent<Rigidbody>();Start()Awake()
  2. 您可以通过编程方式添加组件,并使用一行代码同时分配变量:rb = AddComponent<RigidBody>();

进一步说明:如果您希望 Unity 向对象添加组件,并且您可能忘记添加组件,则可以在类声明上方键入(所有使用s 的空格下方)。[RequireComponent(typeof(RigidBody))]

享受并享受制作游戏的乐趣!

15赞 4 revs, 3 users 68%M.Hassan #26

您可以在 C# 6 中使用 Null 条件运算符以干净的方式进行修复,并编写更少的代码来处理 null 检查。NullReferenceException

它用于在执行成员访问 (?.) 或索引 (?[) 操作之前测试 null。

  var name = p?.Spouse?.FirstName;

它等同于:

    if (p != null)
    {
        if (p.Spouse != null)
        {
            name = p.Spouse.FirstName;
        }
    }

结果是,当 p 为 null 或 null 时,名称将为 null。p.Spouse

否则,将为变量名称分配 的值。p.Spouse.FirstName

有关更多详细信息:Null 条件运算符