什么是NullReferenceException,如何解决?

这篇文章是 社区维基 。 编辑现有答案以改善此职位。 它当前不接受新的答案。

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

你调用的对象是空的。

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


#1楼

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

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

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

一个示例就是带有Calendar控件的简单ASP.NET绑定片段:

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

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


#2楼

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

例:

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

异常错误是:

未处理的异常:

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


#3楼

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

在这里, BookCar是不兼容的类型; 不能将Car转换/铸造为Book 。 如果此转换失败,则as返回null 。 此后使用mybook会导致NullReferenceException

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

如果您期望类型转换始终成功(即您知道对象应该提前),则应使用强制转换:

ComicBook cb = (ComicBook)specificBook;

如果您不确定的类型,但你想尝试使用它作为一个特定的类型,然后使用as

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

#4楼

更新C#8.0,2019:可空引用类型

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


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

例:

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

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

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

要完全理解为什么会引发NullReferenceException,了解值类型和[引用类型] [3]之间的区别非常重要。

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

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

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

  • 动态
  • 宾语

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

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

#5楼

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

请参阅“ 测试自定义AuthorizationAttribute时抛出的NullReferenceException ”,以获取详细说明。


#6楼

添加一种情况,即实体框架中使用的实体的类名与Web表单代码隐藏文件的类名相同。

假设您有一个Web窗体Contact.aspx,其代码背后的类是Contact,并且您有一个实体名称Contact。

然后,当您调用context.SaveChanges()时,以下代码将引发NullReferenceException。

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

为了完整性DataContext类

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

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

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

当实体和codebehind类都在同一名称空间中时,将发生错误。 若要解决此问题,请为Contact.aspx重命名实体类或代码隐藏类。

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

为了讨论,请查看DbContext.saveChanges()中的NullReferenceException


#7楼

虽然是什么原因导致一个NullReferenceException异常和方法,以避免/修复这样的例外在其他的答案已经得到解决,很多程序员还没有学会的是如何调试自主开发过程中这样的例外。

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


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

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

现在,当抛出(或未处理)NullReferenceException时,调试器将在发生异常的行上停止(是否记住上面设置的规则?)。 有时,错误很容易发现。

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

var x = myString.Trim();

在更高级的情况下,例如以下情况,您将需要使用上述技术之一(“监视”或“即时Windows”)来检查表达式以确定str1为null或str2为null。

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

一旦其中的例外是抛出已经找到,它通常是微不足道的理由向后找出空值[错误]介绍-

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


1如果“抛出时中断”过于激进,并且调试器在.NET或3rd-party库中的NPE上停止,则可以使用“ 用户未处理时中断”来限制捕获的异常。 另外,VS2012引入了“我的代码” ,我建议也启用它。

如果在启用“仅我的代码”的情况下进行调试,则行为会稍有不同。 启用“仅我的代码”后,调试器将忽略在“我的代码”之外抛出并且不会通过“我的代码”的第一时间公共语言运行时(CLR)异常


#8楼

当我们尝试访问null对象的Properties或当字符串值变为空并且试图访问字符串方法时,将引发NullReferenceException

例如:

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

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

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

#9楼

我对此有不同的看法。 这种回答“我还能做些什么来避免呢?

跨不同的层工作时(例如在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();
    }
}

#10楼

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

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

object (或从类System.ValueTypeSystem.Enum或从接口类型) 值类型(除Nullable<> )的拆箱转换( System.ValueType )本身会给出NullReferenceException

在另一个方向上, HasValue等于falseNullable<> 引用类型的装箱转换可以提供null引用,该引用随后可以导致NullReferenceException 。 经典示例是:

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

有时拳击会以另一种方式发生。 例如,使用这种非通用扩展方法:

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

以下代码会出现问题:

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

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


#11楼

NullReference异常— Visual Basic

Visual BasicNullReference ExceptionC#中的 NullReference Exception相同。 毕竟,它们都报告了它们都使用的.NET Framework中定义的相同异常。 Visual Basic特有的原因很少(也许只有一个)。

该答案将使用Visual Basic术语,语法和上下文。 使用的示例来自大量过去的Stack Overflow问题。 这是通过使用帖子中经常出现的各种情况来最大化相关性。 还为可能需要的人提供了更多解释。 此处可能列出与您类似的示例。

注意:

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

基本意义

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

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

找出原因

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

IDE调试显示

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

Catch中的一个MsgBoxError while...时显示Error while...将无济于事。 这种方法还会导致非常糟糕的堆栈溢出问题,因为您无法描述实际的异常,所涉及的对象甚至发生异常的代码行。

您还可以使用Locals WindowDebug-> Locals Window > Locals )检查对象。

一旦知道了问题所在和出处,通常比发布新问题要容易得多,而且速度也更快。

也可以看看:

实例和补救措施

类对象/创建实例

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 ,该变量仅在该上下文(子)中存在。 该reg与模块级的可变Scope ,您将使用在其他地方仍然Nothing

缺少New运算符是导致NullReference Exceptions的#1原因,在所检查的堆栈溢出问题中可见。

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

需要明确的是, Dim (或Private )仅声明一个变量及其Type 。 变量的作用域 -是否在整个模块/类中存在,还是过程的局部变量-由声明位置确定。 Private | Friend | Public Private | Friend | Public定义访问级别,而不是Scope

有关更多信息,请参见:


数组

数组也必须实例化:

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

有关更多信息,请参见:


清单和收藏

.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 ,而不实例化了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 Dataset对象,但是从未创建实例。 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尝试访问它。 另一个潜在的问题是假设会有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
   ...

该代码仅用于测试ItemsmyFooBar可能为Nothing。 补救措施是一次测试一个对象的整个链或路径:

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

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

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

无法引用null对象的任何“下游”对象。 这也适用于控件:

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

在这里, myWebBrowserDocument可能为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的情况。 这也很复杂,因为它可能导致失败的级联。

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

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

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

由于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没有提供警告,但补救措施是在窗体级别声明容器,但在控件确实存在时在窗体加载事件处理程序中初始化它们。 只要您的代码在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找不到容器控件中的任何控件(如GroupBoxPanel )。 它们将位于该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...})

函数什么都不返回

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时,任何尝试使用返回值的someCondition = False都将导致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)可能会隐藏问题所在并导致新问题:

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 null的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函数用于测试值是否等于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

如果找不到(或存在于GroupBox )带有chkNameCheckBox ,则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 AllowUserToAddRowsTrue (默认值)时,底部空白/新行中的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集合。 另一种解决方法是在“ 项目”->“设置” |“设置”中为您的集合添加初始值 FooBars ,保存项目,然后删除假值。


关键点

您可能忘记了New运算符。

要么

您认为可以完美执行的操作可以将已初始化的对象返回到您的代码中,而事实并非如此。

永远不要忽略编译器警告,而要始终使用Option Strict On


MSDN NullReference异常


#12楼

关于“我该怎么办” ,可能会有很多答案。

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

简而言之, 类不变式可确保您的类中存在一些在正常使用中不会受到违反的约束(因此,该类不会处于不一致的状态)。 前提条件意味着作为函数/方法的输入提供的数据必须遵循一定的约束条件,并且永不违反它们; 而后置条件意味着函数/方法的输出必须再次遵循设置的约束条件,而又从未违反它们。 合同条件应该免费的错误,程序的执行过程中被侵犯,因此契约式设计在调试模式实践中被选中,而在释放禁用 ,最大限度地开发的系统性能。

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

public X { get; set; }

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

但是,如果将“属性X绝不能具有空值”设置为方法前提,则可以避免上述情况:

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

因此,存在用于.NET应用程序的Code Contracts项目。

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

更新:值得一提的是,该术语是由Bertrand Meyer 与其Eiffel编程语言的设计有关的


#13楼

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


当我尝试通过向其发送模型来在视图中呈现视图时,得到的Object reference not set to an instance of an object ,如下所示:

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

调试显示MyOtherView中的模型为Null。 直到我将其更改为:

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

而且有效。

此外,我之所以没有Html.Partial开始用,是因为Visual Studio中有时下抛出错误,寻找波浪线Html.Partial如果它是一个不同的方式构造内foreach循环,即使它不是一个真正的错误:

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

但是我能够运行该应用程序而不会出现“错误”问题。 我可以通过将foreach循环的结构更改为如下形式来消除该错误:

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

尽管我有一种感觉,这是因为Visual Studio误读了&括号。


#14楼

好吧,简单来说:

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

那么如何解决这个问题:

  1. 调试并让调试器中断...它将直接带您到已损坏的变量...现在您的任务是简单地解决此问题。在适当的位置使用new关键字。

  2. 如果它是由于不存在该对象而在某些数据库命令上引起的,那么您所需要做的就是进行空检查并处理它:

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

通过名称查找,我的意思是某些框架允许您使用字符串来查找对象,并且代码可能如下所示: FindObject(“ ObjectName”);


#15楼

你能为这个做什么?

这里有很多很好的答案,解释什么是空引用以及如何调试它。 但是,如何预防或至少使问题更容易解决的问题很少。

检查参数

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

ArgumentNullException的构造ArgumentNullException甚至将参数名和一条消息作为参数,以便您可以确切地告诉开发者问题所在。

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

使用工具

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

在“ Microsoft代码合同”中,您使用诸如Contract.Requires(obj != null)类的语法,该语法为您提供运行时和编译检查: 代码合同简介

还有“ PostSharp”,将允许您仅使用如下属性:

public void DoSometing([NotNull] obj)

这样,将PostSharp纳入构建过程的一部分,将在运行时检查obj是否为null。 请参阅: PostSharp空检查

普通代码解决方案

或者,您始终可以使用简单的旧代码编写自己的方法。 例如,这里是一个可用于捕获空引用的结构。 它以与Nullable<T>相同的概念建模:

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

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

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

            _value = value;
        }
    }

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

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

您将使用与Nullable<T>相同的方法,除了以完全相反的目的为目标-不允许null 。 这里有些例子:

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

NotNull<T>T隐式转换,因此您可以在任何需要的地方使用它。 例如,您可以将Person对象传递给采用NotNull<Person>

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

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

如您在上面看到的具有nullable的那样,您将通过Value属性访问基础值。 另外,您可以使用显式或隐式强制转换,可以在下面看到带有返回值的示例:

Person person = GetPerson();

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

或者,当方法通过执行Person仅返回T (在这种情况下为Person )时,甚至可以使用它。 例如,以下代码与上面的代码类似:

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

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

与扩展结合

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

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

        return @this;
    }
}

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

var person = GetPerson().NotNull();

的GitHub

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

https://github.com/luisperezphd/NotNull

相关语言功能

C#6.0引入了“空条件运算符”,它对此有所帮助。 使用此功能,您可以引用嵌套对象,如果其中任何一个为null则整个表达式将返回null

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

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

想象一下, country是“ Country ”类型的对象,它具有一个名为“ State的属性,依此类推。 如果countryStateCountyCitynulladdress will be. Therefore you only have to check whether . Therefore you only have to check whether地址. Therefore you only have to check whether is空。

这是一个很棒的功能,但是它给您的信息较少。 并没有使4中的哪个为空并不明显。

像Nullable一样内置?

C#有一个很好的Nullable<T>速记,您可以通过在问号后面加一个问号使int?变为可空值int?

如果C#上面有类似NotNull<T>结构,并且有类似的速记(也许是感叹号(!)),那么您可以这样写: public void WriteName(Person! person)


#16楼

未实例化要使用的类的对象时,会发生未设置为对象实例的NullReferenceException或Object引用。 例如:

假设您有一个名为Student的课程。

public class Student
{
    private string FirstName;
    private string LastName;
    public string GetFullName()
    {
        return FirstName + LastName;
    }
}

现在,考虑在另一个班级尝试检索学生的全名。

public class StudentInfo
{      
    public string GetStudentName()
    {
        Student s;
        string fullname = s.GetFullName();
        return fullname;
    }        
}

如以上代码所示,语句Student s-仅声明了Student类型的变量,请注意,此时尚未实例化Student类。 因此,当执行s.GetFullName()语句时,它将引发NullReferenceException。


#17楼

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

例如:假设您有一个名为myClass的类,它包含一个属性prop1。

public Class myClass
{
   public int prop1 {get;set;}
}

现在,您可以像下面这样在其他一些类中访问此prop1:

public class Demo
{
     public void testMethod()
     {
        myClass ref = null;
        ref.prop1 = 1;  //This line throws error
     }
}

上面的行引发错误,因为声明了类myClass的引用但未实例化,或者没有将对象的实例分配给该类的Referecne。

要解决此问题,您必须实例化(将对象分配给该类的引用)。

public class Demo
{
     public void testMethod()
     {
        myClass ref = null;
        ref = new myClass();
        ref.prop1 = 1;  
     }
}

#18楼

有趣的是,此页面上的答案均未提及这两种情况,如果我添加它们,希望没人介意:

边缘案例1:同时访问字典

.NET中的通用词典不是线程安全的,当您尝试从两个并发线程访问键时,它们有时可能会抛出NullReference甚至是(更常见) KeyNotFoundException 。 在这种情况下,例外情况非常容易引起误解。

边缘案例2:不安全的代码

如果unsafe代码引发了NullReferenceException则可能会查看指针变量,并检查它们的IntPtr.Zero或其他内容。 这是同一件事(“空指针异常”),但是在不安全的代码中,变量通常被强制转换为值类型/数组等,然后您将头撞墙,想知道值类型如何抛出该值例外。

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


#19楼

有可能发生与有关的情况。 在我提出解决方案之前,问题最终变得封闭: https : //stackoverflow.com/questions/43348009/unable-to-instantiate-class

注意不要实例化的类:如果类中构造函数的任何部分抛出null reference exception则该类不会实例化。 以我为例,它试图从web.config获取不存在的连接字符串。

我实例化了一个类:

ClassName myClass = new ClassName();
myClass.RunSomeMethod();

类本身内部是一个从web.config获取连接字符串的调用。 构造函数的这一部分引发了null值异常,因此myClass为null。

如果遇到无法实例化类的情况,请尝试确保类构造函数的任何部分都不会抛出null value exception 。 F-11并逐步完成课程,确保没有空值。


#20楼

这基本上是一个Null引用异常 。 如Microsoft所述-

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

这意味着什么?

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

“嘿,等等,该成员没有值,因此它无法执行您要移交给它的任务。”

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

如果我们使用值类型成员,则不会发生NullReferenceException。

class Program
{
    static void Main(string[] args)
    {
        string str = null;
        Console.WriteLine(str.Length);
        Console.ReadLine();
    }
}

上面的代码显示了一个简单的字符串,该字符串分配了一个值。

现在,当我尝试打印字符串str的长度时,我得到了一条'System.NullReferenceException'类型的未处理的异常消息,因为成员str指向null并且不能有任何长度的null。

当我们忘记实例化引用类型时,也会发生' NullReferenceException '。

假设我有一个类和成员方法。 我没有实例化我的班级,而只是命名我的班级。 现在,如果我尝试使用该方法,编译器将抛出错误或发出警告(取决于编译器)。

class Program
{
    static void Main(string[] args)
    {
        MyClass1 obj;
        obj.foo();  //Use of unassigned local variable 'obj'
    }
}

public class MyClass1
{
    internal void foo()
    {
        Console.WriteLine("hello from foo");

    }
}

上面代码的编译器会引发一个错误,即未分配变量obj ,这表明我们的变量具有空值或什么都没有。 上面代码的编译器会引发一个错误,即未分配变量obj ,这表明我们的变量具有空值或什么都没有。

为什么会发生?

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

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

如何避免呢?

有多种方法和方法可以避免这种著名的异常:

  1. 显式检查:我们应该遵循检查对象,属性,方法,数组和集合是否为空的传统。 可以使用if-else if-else等条件语句简单地实现。

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

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

  4. 调试器:对于开发人员而言,我们拥有进行调试的强大武器。 如果在开发过程中遇到NullReferenceException,则可以使用调试器来获取异常源。

  5. 内置方法:GetValueOrDefault(),IsNullOrWhiteSpace()和IsNullorEmpty()之类的系统方法检查是否为空,如果存在空值,则分配默认值。

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

希望这也能有所帮助!


#21楼

如果我们考虑可能引发此异常的常见情况,请在顶部使用对象访问属性。

例如:

string postalcode=Customer.Address.PostalCode; 
//if customer or address is null , this will through exeption

在这里,如果address为null,那么您将获得NullReferenceException。

因此,在实践中,在访问此类对象的属性(特别是通用属性)之前,我们应始终使用null检查

string postalcode=Customer?.Address?.PostalCode;
//if customer or address is null , this will return null, without through a exception

#22楼

这意味着您的代码使用了设置为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
}

#23楼

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

SqlConnection connection = null;
connection.Open();

这将引发错误,因为当我声明变量“ connection ”时,它没有指向任何东西。 当我尝试将成员称为“ Open ”时,没有可解决的引用,它将引发错误。

为避免此错误:

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

JetBrains的Resharper工具将识别代码中可能存在空引用错误的每个位置,从而使您可以进行空检查。 此错误是错误的第一来源,恕我直言。


#24楼

原因是什么?

底线

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

像其他任何东西一样, null会被传递出去。 如果方法“ A”中为null ,则可能是方法“ B” 方法“ A”传递了null

null可以具有不同的含义:

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

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

进一步来说

运行时抛出NullReferenceException 总是意味着同一件事:您正在尝试使用引用,并且该引用未初始化(或者曾经被初始化,但是不再被初始化)。

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

string foo = null;
foo.ToUpper();

这将在第二行抛出NullReferenceException ,因为您不能在指向nullstring引用上调用实例方法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.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)空引用,可以在父(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中按照它们在视觉树中出现的顺序创建的。 对于带有事件处理程序等的早期创建的控件,将引发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>

在这里, comboBox1label1之前创建。 如果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本身就是NULL)。 因此请注意。

LINQ FirstOrDefault()和SingleOrDefault()

没有任何内容时,普通版本的First()Single()引发异常。 在这种情况下,“ OrDefault”版本返回null。 因此请注意。

前言

当您尝试迭代null集合时, foreach引发。 通常由返回集合的方法的意外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;
}

如果值永远不应为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

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

使用空条件运算符: ?.?[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 ,如果person.Titlenull则不会调用ToUpper

当然,您仍然必须检查title是否为null或将null条件运算符与null合并运算符( ?? )一起使用以提供默认值:

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

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

在C#8中引入了null上下文和nullable引用类型,它们对变量执行静态分析,并在值可能为null或已设置为null时提供编译器警告。 可为空的引用类型允许将类型明确地允许为null。

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

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

一个:可空引用类型是使用相同的语法空值类型注意到? 附加到变量的类型。

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

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结果为nullMakeFrob将抛出。 现在,您可能会认为正确的做法是:

// 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检查,可以防止null取消引用,但是可以将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的参考源,您会发现该技术始终被使用。 编写起来有点笨拙,但是它使调试null错误更加容易。 优化代码以方便调用者,而不是作者

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

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

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

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

要了解为什么会这样,它有助于首先了解.NET如何产生null取消引用异常。 (这些详细信息适用于Windows上运行的.NET;其他操作系统使用类似的机制。)

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

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

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

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

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

如果您使用的是不安全的代码,并且会收到null解除引用异常,则请注意,有问题的指针不必为null。 它可以在最低页面的任何位置,并且将产生此异常。


#25楼

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

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

您要么从未为变量分配任何东西,从未创建分配给该变量的值的实例,要么手动将变量设置为Nothing / null ,或者调用了一个为您将变量设置为Nothing / null的函数。


#26楼

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


#27楼

如果在保存或编译内部版本时收到此消息,请关闭所有文件,然后打开任何文件进行编译和保存。

对我来说,原因是我已重命名该文件,而旧文件仍处于打开状态。


#28楼

要使用方法和对象的成员,您首先必须创建该对象。 如果没有创建它(应该保存该对象的变量未初始化),但是尝试使用它的方法或变量,则会收到该错误。

有时您可能只是忘记进行初始化。

编辑: new不能返回null,但是失败时会触发异常。 很久以前,某些语言就是这种情况,但现在已经不复存在了。 感谢@John Saunders指出这一点。


#29楼

从字面上看,修复NullReferenceExeption的最简单方法有两种。 例如,如果您有一个GameObject,它附带有脚本和一个名为rb(rigidbody)的变量,则在您开始游戏时该变量将开始为null。
这就是为什么获得NullReferenceExeption的原因,因为计算机没有在该变量中存储数据。

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

  1. 使用AddComponent> Physics> Rigidbody将RigidBody添加到您的对象
    然后进入脚本并键入rb = GetComponent<Rigidbody>();
    此代码行在Start()Awake()函数下效果最佳。
  2. 您可以通过编程添加组件,并使用一行代码同时分配变量: rb = AddComponent<RigidBody>();

附加说明:如果要统一向对象添加组件,并且可能忘记添加一个组件,则可以在类声明上方(所有用法下方的空格[RequireComponent(typeof(RigidBody))]键入[RequireComponent(typeof(RigidBody))]
享受游戏乐趣!


#30楼

您可以使用c#6中的空条件运算符以干净的方式修复NullReferenceException,并编写较少的代码来处理空检查。

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

  var name = p?.Spouse?.FirstName;

等效于:

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

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

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

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

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页