我有一些代码,当它执行时,它会抛出NullReferenceException,说:
对象引用未设置为对象的实例。
这意味着什么,我可以做什么来修复这个错误?
我有一些代码,当它执行时,它会抛出NullReferenceException,说:
对象引用未设置为对象的实例。
这意味着什么,我可以做什么来修复这个错误?
当前回答
另一种可能发生NullReferenceExceptions的情况是(不正确)使用as运算符:
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
在这里,Book和Car是不兼容的类型;汽车不能转换成书。当此强制转换失败时,as返回null。在此之后使用mybook会导致NullReferenceException。
通常,应使用强制转换或,如下所示:
如果您希望类型转换总是成功的(即,您知道对象应该是什么),那么应该使用强制转换:
ComicBook cb = (ComicBook)specificBook;
如果您不确定该类型,但希望尝试将其用作特定类型,请将其用作:
ComicBook cb = specificBook as ComicBook;
if (cb != null) {
// ...
}
其他回答
请注意,无论情况如何,.NET中的原因总是相同的:
您正在尝试使用值为Nothing/null的引用变量。当引用变量的值为Nothing/null时,这意味着它实际上没有保存对堆上存在的任何对象实例的引用。您要么从未向变量赋值,要么从未创建分配给变量的值的实例,要么手动将变量设置为Nothing/null,要么为您调用了将变量设置成Nothing/nnull的函数。
NullReference异常-Visual Basic
Visual Basic的NullReference异常与C#中的异常没有区别。毕竟,它们都报告了各自使用的.NET Framework中定义的相同异常。Visual Basic特有的原因很少(可能只有一个)。
此答案将使用Visual Basic术语、语法和上下文。所使用的示例来自过去的大量堆栈溢出问题。这是为了通过使用帖子中经常出现的各种情况来最大限度地提高相关性。还为可能需要的人提供了更多的解释。这里很可能列出了一个类似于您的示例。
注:
这是基于概念的:没有代码可以粘贴到项目中。它旨在帮助您了解导致NullReferenceException(NRE)的原因,如何查找它,如何修复它,以及如何避免它。NRE可以通过多种方式导致,因此这不可能是您唯一的遭遇。这些例子(来自StackOverflow文章)并不总是首先展示了做某事的最佳方式。通常,使用最简单的补救方法。
基本含义
消息“Object not set to a instance of Object”表示您正在尝试使用尚未初始化的对象。这归结为以下之一:
您的代码声明了一个对象变量,但它没有初始化它(创建一个实例或“实例化”它)您的代码假设会初始化对象,而不是可能是其他代码过早地使仍在使用的对象无效
查找原因
由于问题是一个Nothing的对象引用,所以答案是检查它们以找出哪一个。然后确定未初始化的原因。将鼠标放在各种变量上,VisualStudio(VS)将显示它们的值——罪魁祸首将是Nothing。
您还应该从相关代码中删除任何Try/Catch块,尤其是Catch块中没有任何内容的代码。这将导致代码在尝试使用Nothing对象时崩溃。这是您想要的,因为它将确定问题的确切位置,并允许您确定导致问题的对象。
Catch中显示Error while…的MsgBox。。。不会有什么帮助。这种方法也会导致非常糟糕的堆栈溢出问题,因为您无法描述实际的异常、所涉及的对象,甚至无法描述发生异常的代码行。
您还可以使用Locals窗口(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)中。您将在其他地方使用的具有模块级作用域的reg变量仍然为Nothing。
缺少New运算符是出现NullReference异常的头号原因,在所审查的堆栈溢出问题中可以看到。Visual Basic尝试使用New反复使过程清晰:使用New运算符创建一个新对象,并调用Sub New(构造函数),对象可以在其中执行任何其他初始化。
明确地说,Dim(或Private)只声明变量及其类型。变量的作用域(是否存在于整个模块/类或过程的本地)由其声明的位置决定。Private | Friend | Public定义访问级别,而不是Scope。
有关详细信息,请参阅:
新建操作员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}
数据类型和数组大小是从分配的数据中推断出来的。类/模块级声明仍然需要具有Option Strict的As<Type>:
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)
一个常见的监督是使用集合类型的类:
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
有关详细信息,请参见列表(T类)。
数据提供程序对象
使用数据库为NullReference提供了许多机会,因为可以同时使用许多对象(命令、连接、事务、数据集、数据表、数据行……)。注意:无论您使用哪种数据提供程序(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-Dataset对象,但从未创建实例。DataAdapter将填充现有数据集,而不是创建数据集。在这种情况下,由于ds是一个局部变量,IDE警告您可能会发生这种情况:
当声明为模块/类级变量时,就像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)
这里有一个错别字:员工与员工。未创建名为“Employee”的DataTable,因此尝试访问该DataTable时会出现NullReferenceException。另一个潜在问题是,假设SQL包含WHERE子句时,存在可能不是这样的Item。
救济
由于这使用一个表,因此使用表(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是一个返回受影响行数的函数,也可以进行测试:
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
....
而且也很重要。一旦遇到第一个False条件,将不会执行后续测试。这允许代码一次安全地“钻取”对象一个“级别”,仅在myFoo被确定为有效之后(并且如果)才计算myFoo.Bar。对复杂对象进行编码时,对象链或路径可能会很长:
myBase.myNodes(3).Layer.SubLayer.Foo.Files.Add("somefilename")
不能引用空对象的任何“下游”。这也适用于控制:
myWebBrowser.Document.GetElementById("formfld1").InnerText = "some value"
此处,myWebBrowser或Document可能为Nothing或formfld1元素可能不存在。
用户界面控件
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将报告控件在当前上下文中不存在,或者“无法引用非静态成员”。因此,在某种程度上,这是一种只使用VB的情况。它也很复杂,因为它可能导致故障级联。
无法以这种方式初始化数组和集合。此初始化代码将在构造函数创建窗体或控件之前运行。因此:
列表和集合将为空数组将包含五个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)中的任何控件;它们将位于该Panel或GroupBox的Controls集合中。当控件名称拼写错误(“TeStBox2”)时,也不会返回控件。在这种情况下,Nothing将再次存储在这些数组元素中,当您尝试引用它时,将产生NRE。
既然您知道自己在寻找什么,这些应该很容易找到:
“Button2”位于面板上
救济
使用控件引用,而不是使用窗体的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”。您可以通过将Exit Function替换为Return Nothing来抑制警告,但这并不能解决问题。当someCondition=False时,任何试图使用返回的都将导致NRE:
bList = myFoo.BarList()
For Each b As Bar in bList ' EXCEPTION
...
救济
用Return bList替换函数中的Exit Function。返回空列表与返回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尝试执行清理,但由于无法关闭空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值表示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
如果找不到具有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(默认值)时,底部空白/新行中的单元格将全部包含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循环,请在IsNewRow为true时修改行计数或使用Exit For。
My.Settings(StringCollection)
在某些情况下,尝试使用My.Settings中的StringCollection项可能会在第一次使用它时导致NullReference。解决方案是相同的,但不是很明显。考虑:
My.Settings.FooBars.Add("ziggy") ' foobars is a string collection
由于VB正在为您管理设置,因此期望它初始化集合是合理的。它会,但前提是您之前已经向集合中添加了初始条目(在设置编辑器中)。由于集合在添加项时(显然)已初始化,因此当“设置”编辑器中没有要添加的项时,集合将保持为“无”。
救济
如果需要,请在表单的Load事件处理程序中初始化设置集合:
If My.Settings.FooBars Is Nothing Then
My.Settings.FooBars = New System.Collections.Specialized.StringCollection
End If
通常,“设置”集合只需要在应用程序首次运行时初始化。另一种补救方法是在Project->Settings|FooBars中向集合添加初始值,保存项目,然后删除假值。
要点
你可能忘记了New操作符。
or
您假设会完美地执行某些操作,将初始化的对象返回到代码中,但没有。
不要忽略编译器警告(永远)并使用Option Strict On(永远)。
MSDN NullReference异常
您正在使用包含空值引用的对象。所以它给出了一个空异常。在本例中,字符串值为空,在检查其长度时发生异常。
例子:
string value = null;
if (value.Length == 0) // <-- Causes exception
{
Console.WriteLine(value); // <-- Never reached
}
异常错误为:
未处理的异常:System.NullReferenceException:对象引用未设置为实例对象的。位于Program.Main()
如果在保存或编译构建过程中收到此消息,只需关闭所有文件,然后打开任何文件进行编译和保存即可。
对我来说,原因是我重命名了文件,而旧文件仍然打开。
我有不同的观点来回答这个问题。这种回答是“我还能做什么来避免它?”
当跨不同层工作时,例如在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();
}
}