题 什么是NullReferenceException,我该如何解决?


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

你调用的对象是空的。

这是什么意思,我该怎么做才能解决这个错误?


1878


起源


VS 2017中的异常助手将更有助于诊断此异常的原因 - blogs.msdn.microsoft.com/visualstudio/2016/11/28/... 下 新的例外助手。 - Zev Spitz
我们只是说“对象尚未初始化”?所以如果你有一个变量声明:SomeClass myVariable;这将创建myVariable并引用SomeClass,但它未初始化并且将等于null。您必须通过执行SomeClass myVariable = new SomeClass();来调用类初始化。或者,如果要返回对另一个变量的引用:SomeClass myVariable = anotherVariableDeclared(); - Arvin Amir
@Arvin怎么样?MfClass - John Saunders
亲爱的未来访客,这个问题的答案同样适用于 ArgumentNullException。如果您的问题已作为此问题的副本关闭,并且您遇到ANE,请按照答案中的说明进行调试并解决问题。 - Will
只有在将null作为参数传递时才会发生@will ANE。如果ANE问题与此问题相同,您能举例说明吗? - John Saunders


答案:


原因是什么?

底线

你正在尝试使用的东西 null (要么 Nothing 在VB.NET中)。这意味着您要么将其设置为 null,或者你从来没有把它设置成任何东西。

像其他任何东西, null 得到传递。如果是 null   方法“A”,可能是方法“B”通过了 null   方法“A”。

null 可以有不同的含义:

  1. 对象变量是 未初始化 因此 什么都没有。 在这种情况下,如果访问此类对象的属性或方法,则会导致a NullReferenceException
  2. 开发人员是 运用 null 故意表明没有有意义的价值。 请注意,C#具有变量的可空数据类型的概念(如数据库表可以有可空字段) - 您可以分配 null 例如,向他们表明没有存储在其中的值 int? a = null; 其中问号表示允许在变量中存储null a。你可以用它来检查 if (a.HasValue) {...} 或者 if (a==null) {...}。可空的变量,如 a 这个例子,允许通过访问值 a.Value 明确地,或者正常通过 a
    注意 通过访问它 a.Value 抛出一个 InvalidOperationException 代替 NullReferenceException 如果 a 是 null  - 您应该事先进行检查,即如果您有另一个可以为空的变量 int b; 然后你应该做像 if (a.HasValue) { b = a.Value; } 或更短 if (a != null) { b = a; }

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

进一步来说

运行时抛出一个 NullReferenceException  总是 意思是同一件事:你试图使用一个引用,并且引用没有被初始化(或者它是 一旦 初始化,但是 不再 初始化)。

这意味着参考是 null,并且您无法通过a访问成员(例如方法) null 参考。最简单的情况:

string foo = null;
foo.ToUpper();

这将抛出一个 NullReferenceException 在第二行,因为你无法调用实例方法 ToUpper() 在...上 string 引用指向 null

调试

你如何找到一个来源 NullReferenceException?除了查看异常本身,它将完全抛出它发生的位置,Visual Studio中的调试的一般规则适用:放置战略断点和 检查你的变量,将鼠标悬停在其名称上,打开(快速)监视窗口或使用各种调试面板(如本地和汽车)。

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

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

例子

可以抛出异常的一些常见场景:

通用

ref1.ref2.ref3.member

如果ref1或ref2或ref3为null,那么你将得到一个 NullReferenceException。如果你想解决这个问题,那么通过将表达式重写为更简单的等价物来找出哪一个是null:

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

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

间接

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)null引用,可以在父(Book)对象的构造函数中初始化它。

嵌套对象初始化器

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

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

这转化为

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

虽然 new 使用关键字,它只创建一个新的实例 Book,但不是新的实例 Person, 所以 Author 该物业仍在 null

嵌套集合初始化器

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

嵌套的集合初始值设定项的行为相同:

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 Person 只创建一个实例 Person,但是 Books 收藏仍然是 null。集合初始值设定项语法不会创建集合 对于 p1.Books,它只能翻译成 p1.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.

活动

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
    }
}

错误的命名约定:

如果您以不同于本地的方式命名字段,您可能已经意识到您从未初始化该字段。

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空视图模型

如果引用属性时发生异常 @Model 在ASP.NET MVC视图中,您需要了解它 Model 在你的行动方法中设定 return 一个看法。从控制器返回空模型(或模型属性)时,视图访问它时会发生异常:

// 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控件是在调用期间创建的 InitializeComponent 按照它们出现在可视树中的顺序。一个 NullReferenceException 将在使用事件处理程序等的早期创建控件的情况下引发 InitializeComponent 它引用了后期创建的控件。

例如 :

<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>

这里 comboBox1 是在之前创建的 label1。如果 comboBox1_SelectionChanged 试图引用`label1,它还没有被创建。

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

更改XAML中声明的顺序(即列表 label1 之前 comboBox1忽视设计哲学问题,至少会解决问题 NullReferenceException 这里。

演员 as

var myThing = someObject as Thing;

这不会抛出InvalidCastException但会返回一个 null 当转换失败时(当someObject本身为null时)。所以要注意这一点。

LINQ FirstOrDefault()和SingleOrDefault()

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

的foreach

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

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

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

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

避免的方法

明确检查 null 并忽略空值。

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

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;
}

明确检查 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.Assert 如果一个值永远不应该 null,以便在发生异常之前捕获问题。

当您在开发期间知道方法可能可以,但永远不应该返回 null, 您可以使用 Debug.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.
}

虽然这个检查 不会在您的发布版本中结束,导致它抛出 NullReferenceException 再来的时候 book == null 在运行时处于发布模式。

使用 GetValueOrDefault() 可以为null的值类型提供默认值 null

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#]或 If() [VB]。

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条件运算符: ?. 要么 ?[x] 对于数组(在C#6和VB.NET 14中可用):

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

var title = person.Title.ToUpper();

如果此人没有标题,则会抛出异常,因为它正在尝试呼叫 ToUpper 在具有空值的属性上。

在C#5及以下版本中,可以保护以下内容:

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

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

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

这将导致title变量 null,和呼吁 ToUpper 如果没有 person.Title 是 null

当然,你 仍然 必须检查 title for null或使用null条件运算符和null coalescing运算符(??)提供默认值:

// 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");

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

在迭代器中调试和修复null derefs的特殊技术

C#支持“迭代器块”(在其他一些流行语言中称为“生成器”)。由于延迟执行,空取消引用异常在迭代器块中调试尤其棘手:

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) { ... }

如果 whatever 结果是 null 然后 MakeFrob 会扔。现在,您可能认为正确的做法是:

// 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();
}

为什么这是错的?因为迭代器块实际上并不存在  直到 foreach!打电话给 GetFrobs 只返回一个对象 迭代时 将运行迭代器块。

通过像这样写一个空检查,你可以防止空取消引用,但是你可以将null参数异常移动到 迭代,不是为了 呼叫, 那就是 调试非常混乱

正确的解决方法是:

// 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();
}

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

如果检查LINQ to Objects的参考源,您将看到始终使用此技术。写入稍微笨拙,但它使调试无效性错误变得更加容易。 优化代码以方便调用者,而不是作者的便利

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

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

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

  • 取消引用null 指针 产生与解除引用null相同的异常 参考
  • 解除引用无效的非空指针 能够 产生那个例外 在某些情况下

要理解为什么这样做,首先要了解.NET如何产生空解除引用异常。 (这些细节适用于在Windows上运行的.NET;其他操作系统使用类似的机制。)

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

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

这就是为什么解除引用空指针和空引用会产生相同的异常。

第二点怎么样?取消引用 任何 落在虚拟内存最低页面中的无效指针会导致相同的操作系统错误,从而导致相同的异常。

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

如果您正在使用不安全的代码并且您获得了空解除引用异常,请注意违规指针不必为空。它可以是最低页面中的任何位置,并且将生成此异常。


2111



也许这是一个愚蠢的评论,但不是第一个和最好的方法来避免这个问题是初始化对象?对我来说,如果发生这个错误,通常是因为我忘了初始化类似数组元素的东西。我认为将对象定义为null然后引用它并不常见。也许给出解决与描述相邻的每个问题的方法。还是一个好帖子。 - JPK
如果没有对象,而是方法或属性的返回值,该怎么办? - John Saunders
这本书/作者的例子有点奇怪......那怎么编译呢? intellisense如何工作?这是什么,我对计算机不好... - Will
@Will:我上次编辑有帮助吗?如果没有,那么请更明确地说明您认为的问题。 - John Saunders
@JohnSaunders哦,不,对不起,我的意思是对象的初始化版本。 new Book { Author = { Age = 45 } }; 内部初始化如何甚至......我无法想到内部init会工作的情况,但它编译和智能感知工作......除非结构? - Will


NullReference异常 - Visual Basic

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

这个答案将使用Visual Basic术语,语法和上下文。使用的示例来自大量过去的Stack Overflow问题。这是为了通过使用最大化相关性  在帖子中常见的情况。还为那些可能需要它的人提供了更多的解释。一个类似于你的例子是 非常 可能在这里列出。

注意:

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

基本含义

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

  • 你的代码 声明 一个对象变量,但它没有 初始化 它(创建一个实例或'实例'它)
  • 你的代码假设的东西会初始化一个对象,但却没有
  • 可能,其他代码过早地使仍在使用的对象无效

寻找原因

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

IDE debug display

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

一个 MsgBox 在显示的Catch中 Error while... 会有点帮助。这种方法也导致了 很坏 Stack Overflow问题,因为你无法描述实际的异常,涉及的对象甚至代码行所在的位置。

你也可以使用 Locals Window (调试 - > Windows - >本地)检查你的对象。

一旦你知道问题是什么以及在哪里,它通常很容易修复,并且比发布新问题更快。

也可以看看:

例子和补救措施

类对象/创建实例

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

问题是 Dim 不会创建CashRegister 目的;它只声明一个名为的变量 reg 那种类型。 声明 一个对象变量并创建一个  是两件不同的事情。

补救

New 在声明实例时,通常可以使用operator来创建实例:

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

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

Private reg As CashRegister
'...

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

这将创建一个 本地 变量, reg,仅存在于该上下文(sub)中。该 reg 模块级别的变量 Scope 你会在其他地方使用它仍然存在 Nothing

错过了 New 运营商是第一个原因 NullReference Exceptions 在Stack Overflow中看到的问题已经过审核。

Visual Basic尝试使用重复清除进程 New: 使用 New 运营商创建一个  对象和电话 Sub New  - 构造函数 - 您的对象可以在其中执行任何其他初始化。

要清楚, Dim (要么 Private) 只要 声明 变量及其变量 Type。该 范围 变量 - 是否存在于整个模块/类中或者是过程的本地变量 - 取决于 哪里 它被宣布。 Private | 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

用一个 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,因为 barList 只是声明,而不是实例化。创建一个实例 Foo 也不会创建内部的实例 barList。可能是在构造函数中执行此操作的意图:

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提供了许多机会,因为可以有许多对象(CommandConnectionTransactionDatasetDataTableDataRows....)立即使用。 注意: 使用哪个数据提供程序无关紧要 - MySQL,SQL Server,OleDB等 - 概念 是相同的。

例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

和以前一样 ds 声明了数据集对象,但从未创建过实例。该 DataAdapter 将填补现有的 DataSet,而不是创造一个。在这种情况下,因为 ds 是一个局部变量, IDE警告你 这可能会发生:

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)

打字错误是一个问题: Employees VS Employee。没有 DataTable 名为“员工”的创建,所以一个 NullReferenceException 试图访问它的结果。另一个潜在的问题是假设会有 Items 当SQL包含WHERE子句时,可能不是这样。

补救

由于这使用一个表,使用 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

DataAdapter 会提供 TableNames 如前面的示例所示,但它不解析SQL或数据库表中的名称。结果是, ds.Tables("TICKET_RESERVATION") 引用一个不存在的表。

补救 是相同的,按索引引用表:

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

也可以看看 DataTable类


对象路径/嵌套

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

代码只是测试 Items 两者都有 myFoo 和 Bar 可能也没什么。该 补救 是一次测试一个对象的整个链或路径:

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

AndAlso 很重要第一次后续测试将不会执行 False 遇到条件。这允许代码一次一个'级别'安全地“钻取”到对象中,进行评估 myFoo.Bar 只有在(如果)之后 myFoo 确定有效。编码复杂对象时,对象链或路径可能会很长:

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

不可能引用任何“下游”的东西 null 目的。这也适用于控件:

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

这里, myWebBrowser 要么 Document 可能是什么都没有 formfld1 元素可能不存在。


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控件中选择某些内容。 ListBox1.SelectedItem 可能是 Nothing所以 ListBox1.SelectedItem.ToString 将导致NRE。

补救

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

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 Forms

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将根据其编码方式进行报告 Controls 在当前上下文中不存在,或“不能引用非静态成员”。所以,在某种程度上,这是一个仅限VB的情况。它也很复杂,因为它可能导致级联故障。

无法以这种方式初始化数组和集合。 此初始化代码将运行 之前 构造函数创建 Form 或者 Controls。结果是:

  • 列表和集合将是空的
  • 该数组将包含Nothing的五个元素
  • somevar 赋值将导致立即NRE,因为Nothing没有 .Text 属性

稍后引用数组元素将导致NRE。如果你这样做 Form_Load,由于一个奇怪的错误,IDE 不得 发生异常时报告异常。弹出异常 后来 当您的代码尝试使用该数组时。这个“沉默的例外”是 在这篇文章中详述。对于我们的目的,关键是当创建表单时发生灾难性事件(Sub New 要么 Form Load 事件),异常可能没有报告,代码退出过程并只显示表单。

因为你的其他代码没有 Sub New 要么 Form Load 事件将在NRE之后运行, 还有很多其他的事情 可以保持未初始化状态。

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 New 只要你的代码在之后 InitializeComponent 呼叫:

' 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

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

现在你知道你在寻找什么,这些应该很容易找到: VS shows you the error of your ways

“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会警告你的情况'并非所有路径都返回值和a NullReferenceException 可能的结果”。您可以通过替换来取消警告 Exit Function 同 Return Nothing,但这并没有解决问题。任何试图使用返回的东西 someCondition = False 将导致NRE:

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

补救

更换 Exit Function 在函数中 Return bList。回来了   List 和回归不一样 Nothing。如果返回的对象有可能存在 Nothing,使用前测试:

 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'之后)导致异常 .ExecuteReader。之后 Catch 什么也没做, Finally 试图进行清理,但既然你做不到 Close 一个空的 DataReader 对象,一个全新的 NullReferenceException 结果。

一个空的 Catch 街区是魔鬼的游乐场。这个OP感到困惑,为什么他要获得NRE Finally 块。在其他情况下,空 Catch 可能会导致更多下游的其他事情变得混乱,并导致您花时间在错误的地方查找错误的问题。 (上述“无声例外”提供相同的娱乐价值。)

补救

不要使用空的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
        ...

IsDBNull 函数用于测试a  等于 System.DBNull来自MSDN:

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

补救

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

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

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 返回第一项或默认值,即 Nothing 对于参考类型,永远不会 DBNull

If getFoo IsNot Nothing Then...

控制

Dim chk As CheckBox

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

如果一个 CheckBox 同 chkName 无法找到(或存在于 GroupBox), 然后 chk 将是Nothing并且试图引用任何属性将导致异常。

补救

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

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"

如果 dgvBooks 具有 AutoGenerateColumns = 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

当你的 DataGridView 具有 AllowUserToAddRows 如 True (默认), Cells 在底部的空白/新行中都将包含 Nothing。大多数尝试使用内容(例如, ToString)将导致NRE。

补救

用一个 For/Each 循环并测试 IsNewRow 确定它是否是最后一行的属性。这是否有效 AllowUserToAddRows 是真是假:

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

如果你确实使用了 For n 循环,修改行数或使用 Exit For 什么时候 IsNewRow 是真的。


My.Settings(StringCollection)

在某些情况下,尝试使用来自的项目 My.Settings 这是一个 StringCollection 第一次使用时可能会导致NullReference。解决方案是相同的,但不是那么明显。考虑:

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

通常, Settings 只需在应用程序第一次运行时初始化集合。另一种方法是在集合中添加初始值 项目 - >设置| FooBars,保存项目,然后删除假值。


关键点

你可能忘记了 New 运营商。

要么

你假设的东西可以完美无瑕地将初始化对象返回到你的代码,但事实并非如此。

不要忽略编译器警告(永远)和使用 Option Strict On (总是)。


MSDN NullReference异常


273





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

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

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

其中一个例子是这个带有Calendar控件的简单ASP.NET绑定片段:

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

这里, SelectedDate 实际上是一个属性 - DateTime 类型 - 的 Calendar Web控件类型,绑定可以完美地返回null。隐式ASP.NET生成器将创建一段代码,它将等同于上面的强制转换代码。这将提出一个 NullReferenceException 这很难被发现,因为它位于ASP.NET生成的代码中,编译很好......


217



很棒的。单线方式避免: DateTime x = (DateTime) o as DateTime? ?? defaultValue; - Serge Shultz


这意味着有问题的变量无效。我可以这样生成:

SqlConnection connection = null;
connection.Open();

这将抛出错误,因为我已经声明变量“connection“,它没有指向任何东西。当我试图给会员打电话时”Open“,没有任何参考可以解决它,它会抛出错误。

要避免此错误:

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

JetBrains的Resharper工具将识别代码中可能存在空引用错误的每个位置,允许您进行空检查。这个错误是错误的头号来源,恕我直言。


146



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


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

为了防止错误,可以在使用之前测试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
}

135





请注意,无论情况如何,.NET中的原因始终相同:

您正在尝试使用值为的引用变量 Nothing/null。当值是 Nothing/null 对于引用变量,这意味着它实际上并不持有对堆上存在的任何对象的实例的引用。

您从未向变量分配内容,从未创建分配给变量的值的实例,或者您将变量设置为等于 Nothing/null 手动,或者您调用了一个将变量设置为的函数 Nothing/null 为你。


90





抛出此异常的一个示例是:当您尝试检查某些内容时,该值为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参数


76