什么是NullReferenceException,以及如何解决它?

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

你调用的对象是空的。

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


原因是什么?

底线

您试图使用null (或VB.NET中的Nothing )。 这意味着你要么将它设置为null ,要么你根本没有设置它。

像其他任何东西一样, null传递。 如果它在方法“A”中为null ,则可能是该方法“B”将null传递给方法“A”。

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

进一步来说

NullReferenceException的运行时总是意味着同样的事情:您试图使用引用,并且引用未初始化(或者它曾经初始化,但不再初始化)。

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

string foo = null;
foo.ToUpper();

这将在第二行中抛出NullReferenceException ,因为您无法在指向null的string引用上调用实例方法ToUpper() 。

调试

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

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

通过这种方式遵循程序流程,您可以找到实例不应该为null的位置,以及为什么它没有正确设置。

例子

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

通用

ref1.ref2.ref3.member

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

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

特别是,在HttpContext.Current.User.Identity.Name , HttpContext.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.
    }
}

如果你想避免child(Person)空引用,你可以在父(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空视图模型

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

// 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中按照它们出现在可视化树中的顺序创建的。 在InitializeComponent引用后期创建的控件的过程中触发事件处理程序等的早期创建控件时,将引发NullReferenceException 。

例如 :

<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中声明的顺序(即,在comboBox1之前列出label1 ,忽略设计原理问题,至少可以解决NullReferenceException 。

用as投射

var myThing = someObject as Thing;

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

LINQ FirstOrDefault()和SingleOrDefault()

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

的foreach

当您尝试迭代空集合时, foreach会抛出。 通常由返回集合的方法导致意外的null结果。

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

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

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

避免的方法

显式检查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;
}

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

当你在开发过程中知道某个方法可能会,但从不应该返回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.
}

虽然此检查不会在您的发行版本中结束,但在发布模式下运行时book == null时,会导致它再次抛出NullReferenceException 。

对于可为null的值类型,使用GetValueOrDefault()可以在它们为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

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

遇到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):

这有时也被称为安全导航或埃尔维斯(在它的形状之后)运营商。 如果运算符左侧的表达式为空,则不会评估右侧,而是返回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 ,并且如果person.Title为null则不会调用ToUpper 。

当然,您仍然需要检查null的title ,或者将空条件运算符与空合并运算符( ?? )一起使用以提供默认值:

// 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个元素。


NullReference异常 - Visual Basic

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

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

注意:

  • 这是基于概念的:没有代码可以粘贴到您的项目中。 它旨在帮助您了解导致NullReferenceException (NRE)的原因,如何找到它,如何解决该问题以及如何避免它。 NRE可能会有很多种方式,所以这不太可能是你唯一的遭遇。
  • 这些例子(来自Stack Overflow的帖子)并不总是展现出首先做某件事的最佳方式。
  • 通常,使用最简单的补救措施。
  • 基本含义

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

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

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

    IDE调试显示

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

    Catch中的MsgBox显示Error while...将没有多大帮助。 这种方法也会导致Stack Overflow问题非常糟糕,因为您无法描述实际的异常,所涉及的对象或者代码行。

    你也可以使用Locals Window ( Debug - > Windows - > Locals )来检查你的对象。

    一旦你知道问题出在什么地方,通常很容易解决问题,并且比发布新问题更快。

    也可以看看:

  • 断点
  • MSDN:如何:使用Try / Catch块来捕获异常
  • MSDN:例外的最佳实践
  • 示例和补救措施

    类对象/创建实例

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

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

    补救

    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
    

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

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

    这将创建一个局部变量reg ,它只存在于该上下文(sub)中。 您将在其他地方使用的模块级别为Scope的reg变量保持为Nothing 。

    New运算符的缺失是NullReference Exceptions的第一个原因,在所检查的Stack Overflow问题中看到。

    Visual Basic尝试使用New重复清除该过程:使用New Operator创建一个新对象并调用Sub New - 构造函数 - 您的对象可以在其中执行任何其他初始化。

    要明确, Dim (或Private )只声明一个变量及其Type 。 变量的范围 - 无论它是存在于整个模块/类还是程序本地的 - 由其声明的位置决定。 Private | Friend | Public Private | Friend | Public定义访问级别,而不是范围。

    有关更多信息,请参阅:

  • 新的操作员
  • 在Visual Basic中的范围
  • 在Visual Basic中访问级别
  • 值类型和引用类型

  • 数组

    数组也必须实例化:

    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 Infer初始化本地数组时, As <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> with 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
    

    有关更多信息,请参阅:

  • 期权推断声明
  • 在Visual Basic中的范围
  • Visual Basic中的数组

  • 列表和集合

    .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提供了很多机会,因为可以同时使用许多对象( Command , Connection , Transaction , Dataset , DataTable , DataRows ....)。 注意:您使用的是哪种数据提供程序无关紧要 - 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 。 没有创建名为“Employee”的DataTable ,因此尝试访问它的NullReferenceException结果。 另一个潜在的问题是假定当SQL包含WHERE子句时,将会出现不可能出现的Items 。

    补救

    由于这使用一个表,所以使用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可能是Nothing。 补救措施是一次测试一个对象的整个链或路径:

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

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

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

    无法引用null对象的任何'下游'。 这也适用于控制:

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

    在这里, myWebBrowser或Document可能是Nothing,或者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窗体

    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没有提供警告,但补救措施是在表单级别声明容器,但在控件确实存在时,在表单加载事件处理程序中初始化它们。 只要您的代码在InitializeComponent调用之后,就可以在Sub New完成:

    ' 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
    

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

    这些应该很容易找到,现在你知道你在找什么: 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...})
    

    函数返回Nothing

    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 '。 您可以通过将Exit Function替换为Return Nothing来取消警告,但这不能解决问题。 当someCondition = False时,任何试图使用return的都会导致NRE:

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

    补救

    使用Return bList替换Exit Function中的Exit Function 。 返回空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很困惑他为什么要在Finally块中获得NRE。 在其他情况下,一个空的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函数用于测试一个值是否等于System.DBNull :从MSDN开始:

    System.DBNull值表示该对象表示丢失或不存在的数据。 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
    

    如果带有chkName的CheckBox无法找到(或存在于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是否为true,这都会起作用:

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

    如果使用For n循环,请修改行数或在IsNewRow为true时使用Exit For 。


    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集合。 另一种补救方法是在Project - > Settings |中为您的收藏添加初始值 FooBars ,保存项目,然后删除假值。


    关键点

    你可能忘了New操作符。

    要么

    你所假设的东西完美无缺地将初始化的对象返回给你的代码,但是没有。

    不要忽略编译器警告(永远)并使用Option Strict On (始终)。


    MSDN NullReference异常


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

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

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

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

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

    在这里, SelectedDate实际上是Calendar Web Control类型的一个DateTime类型的属性,绑定可以完全返回null。 隐式ASP.NET生成器将创建一段代码,该代码将等同于上面的演员代码。 这会引发NullReferenceException ,这是很难发现的,因为它存在于ASP.NET生成的代码中,这些代码编译得很好......

    链接地址: http://www.djcxy.com/p/729.html

    上一篇: What is a NullReferenceException, and how do I fix it?

    下一篇: Storing Objects in HTML5 localStorage