首页 > C# > 在c#中,什么是 NullReferenceException,我该如何解决它?

在c#中,什么是 NullReferenceException,我该如何解决它?

上一篇 下一篇

网友问题:

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

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

这是什么意思,我该怎么做才能修复此错误?

分割线

网友回答:

空引用异常 — Visual Basic

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

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

注意:

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

基本含义

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

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

寻找原因

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

IDE 调试显示

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

A 在 Catch 中显示的帮助不大。这种方法也会导致非常糟糕的堆栈溢出问题,因为您无法描述实际的异常,所涉及的对象甚至发生异常的代码行。MsgBoxError while...

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

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

另请参阅:

  • 断点
  • MSDN:如何:使用 try/catch 块捕获异常
  • MSDN:异常的最佳做法

示例和补救措施

类对象/创建实例

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

问题是不会创建收银机对象;它只声明一个名为该类型的变量。声明对象变量和创建实例是两回事。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 原因,请参阅所审查的堆栈溢出问题。

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

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

使用 a 将使没有有效对象的元素变得非常困难: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,因为仅声明,而不是实例化。创建 的实例不会同时创建内部 .在构造函数中执行此操作可能是意图: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

与以前一样,声明了数据集对象,但从未创建过实例。将填充现有的,而不是创建一个。在这种情况下,由于 是局部变量,IDE 会警告您可能会发生这种情况:dsDataAdapterDataSetds

图像

当声明为模块/类级变量时,就像 一样,编译器无法知道对象是否由上游过程创建。不要忽略警告。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子句时可能并非如此。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

另请参见数据表类。


对象路径/嵌套

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


用户界面控件

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


视觉基本窗体

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

  • 列表和集合将只是空的
  • 数组将包含五个“无”元素
  • 分配将导致立即 NRE,因为“没有任何东西”没有属性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

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

现在您知道要查找的内容,这些应该很容易找到:
VS向你展示你的方式的错误

“按钮 2”驻留在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 可能会隐藏问题所在并导致新的问题:

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 值指示对象表示缺失或不存在的数据。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

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

补救

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

数据网格视图

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"

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

补救

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

dgvBooks.Columns(0).Visible = True

示例 2 — 小心新行

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

当您有 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)

在某些情况下,尝试使用来自 的项可能会导致首次使用它时出现 Null引用。解决方案是相同的,但不是那么明显。考虑: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

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


要点

您可能忘记了操作员。New

您认为可以完美执行以将初始化的对象返回到代码中的东西实际上并非如此。

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


MSDN 空引用异常

分割线

网友回答:

原因是什么?

底线

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

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

null可以有不同的含义:

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

这将在第二行抛出 ,因为您无法在指向 的引用上调用实例方法。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 编译器插入了事件使用情况的空检查,因此没有必要在 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 空视图模型

如果在引用 中的属性时发生异常,则需要了解在视图时在操作方法中设置的 get。从控制器返回空模型(或模型属性)时,视图访问该模型时会发生异常:@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>

Here is created before . If attempts to reference `label1, it will not yet have been created.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(当本身为 null)。所以要注意这一点。InvalidCastExceptionnullsomeObject

林克和FirstOrDefault()SingleOrDefault()

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

福里奇

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

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

遇到 时提供默认值的简写: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();

现在,标题变量将为 null,而不是引发异常。C# 6 为此引入了较短的语法:

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

这将导致标题变量为 ,并且如果为 ,则不调用 。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];

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

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

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

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

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

在迭代器中调试和修复空引用的特殊技术

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

为什么这是错误的?因为迭代器块实际上直到 !对 to 的调用仅返回一个对象,该对象在迭代时将运行迭代器块。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#具有“不安全”模式,顾名思义,这是极其危险的,因为未强制执行提供内存安全和类型安全的正常安全机制。除非你对内存的工作原理有透彻而深刻的理解,否则你不应该编写不安全的代码

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

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

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

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

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

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

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

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

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

分割线

网友回答:

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

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

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

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

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

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

模板简介:该模板名称为【在c#中,什么是 NullReferenceException,我该如何解决它?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【C#】栏目查找您需要的精美模板。

相关搜索
  • 下载密码 lanrenmb
  • 下载次数 290次
  • 使用软件 Sublime/Dreamweaver/HBuilder
  • 文件格式 编程语言
  • 文件大小 暂无信息
  • 上传时间 02-08
  • 作者 网友投稿
  • 肖像权 人物画像及字体仅供参考
栏目分类 更多 >
热门推荐 更多 >
微信模板 响应式 微信公众平台 单页式简历模板 自适应 企业网站 微信素材 微信图片 html5 微信文章
您可能会喜欢的其他模板