提问



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



  你调用的对象是空的。



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

最佳参考


原因是什么?



底线



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


像其他任何东西一样,null被传递。如果方法A中的null ,则可能是方法B将null 传递给方法A。


null可以有不同的含义:



  1. 未初始化的对象变量,因此指向任何内容。在这种情况下,如果您访问此类对象的属性或方法,则会导致NullReferenceException

  2. 开发人员故意使用null来表示没有可用的有意义值。请注意,C#具有变量可空数据类型的概念(如数据库表可以有可空字段) - 您可以为它们分配null以指示其中没有存储值,例如int? a = null;,其中问号表示允许在变量a中存储空值。您可以使用if (a.HasValue) {...}if (a==null) {...}进行检查。可贵的变量,如a这个例子,允许通过a.Value明确地访问值,或者通过a正常访问。
    注意如果anull则通过a.Value访问它会抛出InvalidOperationException而不是NullReferenceException - 你应该事先进行检查,即如果你有另一个可以为空的变量int b;那么你应该做if (a.HasValue) { b = a.Value; }或更短if (a != null) { b = a; }的作业。



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


更具体地说



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


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


string foo = null;
foo.ToUpper();


这将在第二行抛出NullReferenceException,因为你不能在指向nullstring引用上调用实例方法ToUpper()


调试



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


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


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


实施例



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


通用



ref1.ref2.ref3.member


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


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


具体来说,在HttpContext.Current.User.Identity.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.
    }
}


如果要避免子(Person)null引用,可以在父(Book)对象的构造函数中初始化它。


嵌套对象初始值设定项



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


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


这转化为


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


使用new​​]]关键字时,它只创建Book的新实例,但不创建Person的新实例,因此Author属性仍为new​​]] 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.


Jagged Arrays



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.


Activity



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


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


这里label1label1之前创建。如果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。所以要注意这一点。


的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,则可以在访问实例成员之前检查它是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();


这将导致标题变量为null,如果person.Titlenull,则不会调用ToUpper


当然,仍必须检查title为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 th 元素。


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



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


public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
    for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}
...
FrobFactory factory = whatever;
IEnumerable<Frobs> frobs = GetFrobs();
...
foreach(Frob frob in frobs) { ... }


如果whatever导致null,那么MakeFrob将抛出。现在,您可能认为正确的做法是:


// DON'T DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
    if (f == null) 
      throw new ArgumentNullException("f", "factory must not be null");
    for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}


为什么这是错的?因为迭代器块实际上运行直到foreach!对GetFrobs的调用只返回一个对象,当迭代时 将运行迭代器块。


通过写这样的空检查可以防止空取消引用,但是将空参数异常移动到迭代的点,而不是调用,并且这是非常混乱的调试。


正确的解决方法是:


// DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
    // No yields in a public method that throws!
    if (f == null) 
      throw new ArgumentNullException("f", "factory must not be null");
    return GetFrobsForReal(f, count);
}
private IEnumerable<Frob> GetFrobsForReal(FrobFactory f, int count)
{
    // Yields in a private method
    Debug.Assert(f != null);
    for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}


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


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


关于不安全代码中空取消引用的注释



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


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



  • 取消引用null 指针会产生与取消引用null 引用相同的异常

  • 取消引用无效的非空指针可以产生该异常
    在某些情况下



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


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


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


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


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


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


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

其它参考1


NullReference异常 - Visual Basic



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


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


注意:



  1. 这是基于概念的:没有代码可以粘贴到您的项目中。它旨在帮助您了解导致NullReferenceException(NRE)的原因,如何找到它,如何解决它,以及如何避免它。 NRE可以通过多种方式引起,因此这不太可能是您唯一的遭遇。

  2. 示例(来自Stack  Overflow帖子)并不总是显示出最好的方法。

  3. 通常,使用最简单的补救措施。



基本含义



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



  • 您的代码声明一个对象变量,但它没有初始化它(创建一个实例或实例化它)

  • 你的代码假设的东西会初始化一个对象,但

  • 可能,其他代码过早地使仍在使用的对象失效



找出原因



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





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


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


您还可以使用Locals Window(调试 - > Windows - >本地)来检查对象。


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


也可以看看:



  • 断点

  • MSDN:如何:使用Try/Catch Block来捕获例外

  • MSDN:例外的最佳做法



示例和补救措施



类对象/创建实例





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


问题是Dim不会创建CashRegister 对象;它只声明一个名为reg的变量。 声明对象变量并创建实例是两回事。[492] [493] [494]


解决方法


声明时,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


这将创建一个 local 变量reg,该变量仅存在于该上下文(sub)中。模块级别Scopereg变量将在其他地方使用Nothing



  缺少New运算符是在堆栈中看到的NullReference Exceptions 的第一个原因 审查了溢出问题。

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



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


有关更多信息,请参阅:



  • 新操作员

  • Visual Basic中的范围

  • Visual Basic中的访问级别

  • 值类型和引用类型






阵列



数组也必须实例化:[495] [496] [497] [498]


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>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集合(其中有许多变种 - 列表,字典等)也必须实例化或创建。[499] [500] [[501]]


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)类。[[502]]





数据提供程序对象



使用数据库为NullReference提供了许多机会,因为可以有许多对象(CommandConnectionTransactionDatasetDataTableDataRows]] ....)立即使用。 注意:使用哪个数据提供程序无关紧要 - MySQL,SQL Server,OleDB等 - concepts 是相同的。


示例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,而不是创建一个DataSet。在这种情况下,由于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)


输入错误是一个问题: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类。[[503]]





对象路径/嵌套



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


代码仅测试Items,而myFooBar也可能是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 Forms



Public Class Form1

    Private NameBoxes = New TextBox(5) {Controls("TextBox1"), _
                   Controls("TextBox2"), Controls("TextBox3"), _
                   Controls("TextBox4"), Controls("TextBox5"), _
                   Controls("TextBox6")}

    ' same thing in a different format:
    Private boxList As New List(Of TextBox) From {TextBox1, TextBox2, TextBox3 ...}

    ' Immediate NRE:
    Private somevar As String = Me.Controls("TextBox1").Text


这是获得NRE的一种相当常见的方式。在C#中,IDE将根据其编码方式报告Controls在当前上下文中不存在,或无法引用非静态成员。所以,在某种程度上,这是一个仅限VB的情况。它也很复杂,因为它可能导致级联故障。


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



  • 列表和收藏只是空的

  • 数组将包含Nothing
  • 的五个元素
  • somevar赋值将导致立即NRE,因为Nothing没有.Text属性



稍后引用数组元素将导致NRE。如果在Form_Load中执行此操作,由于奇怪的错误,IDE 可能不会报告异常。当您的代码尝试使用该数组时,异常将弹出以后。本文详细介绍了这种沉默的例外。出于我们的目的,关键是当创建表单(Sub NewForm Load事件)发生灾​​难性事件时,异常可能未被报告,代码退出程序并只显示表单。[[504]]


由于您的Sub NewForm 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没有提供警告,但补救方法是在表单级别声明容器,但是当控件<初始化表单加载事件处理程序时strong>做存在。只要您的代码在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。


现在你知道你在寻找什么,这些应该很容易找到:



Button2驻留在Panel


解决方法


不是使用表单s 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可能会导致。您可以通过用Return Nothing替换Exit Function来抑制警告,但这不能解决问题。在someCondition = False时尝试使用返回的任何内容都会导致NRE:


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


解决方法


Return bList替换函数中的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一个空DataReader对象,所以会产生一个全新的NullReferenceException


一个空的Catch区块是魔鬼的操场。这个OP感到困惑,为什么他在Finally区块中得到一个NRE。在其他情况下,空Catch可能会产生其他东西更进一步向下游走下去,导致你花时间在错误的地方看错了问题。(上面描述的无声例外提供了相同的娱乐价值。)


解决方法


不要使用空的Try/Catch块 - 让代码崩溃,以便你可以a)确定原因b)识别位置并c)应用适当的补救措施.Test/Catch块不是为了隐藏来自唯一合格人员的异常解决它们 - 开发人员。





DBNull与Nothing

不同

For Each row As DataGridViewRow In dgvPlanning.Rows
    If Not IsDBNull(row.Cells(0).Value) Then
        ...


IsDBNull函数用于测试值是否等于System.DBNull:来自MSDN:[[505]]



  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 chkName [[或存在于GroupBox中),则chk将为Nothing并且试图引用任何属性将导致例外。


解决方法


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


DataGridView



DGV有一些周期性的怪癖:


dgvBooks.DataSource = loan.Books
dgvBooks.Columns("ISBN").Visible = True       ' NullReferenceException
dgvBooks.Columns("Title").DefaultCellStyle.Format = "C"
dgvBooks.Columns("Author").DefaultCellStyle.Format = "C"
dgvBooks.Columns("Price").DefaultCellStyle.Format = "C"


如果dgvBooksAutoGenerateColumns = 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


DataGridViewAllowUserToAddRowsTrue(默认值)时,底部空白/新行中的Cells将全部包含Nothing。大多数使用内容的尝试(例如,ToString)将导致NRE。


解决方法


使用For/Each循环并测试IsNewRow属性以确定它是否是最后一行。无论AllowUserToAddRows是否为真,这都有效:


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


如果使用For n循环,请修改行计数或在IsNewRow为真时使用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异常[[506]]

其它参考2


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


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


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


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


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


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

其它参考3


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


SqlConnection connection = null;
connection.Open();


这将引发错误,因为当我宣布变量connection时,它没有指向任何东西。当我试图调用成员Open时,没有引用它来解决它,它将抛出错误。


要避免此错误:



  1. 在尝试对象进行任何操作之前,请始终初始化对象。

  2. 如果您不确定对象是否为空,请使用object == null
  3. 进行检查


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

其它参考4


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


为了防止错误,可以在使用之前测试null为null的对象为null。


if (myvar != null)
{
    // Go ahead and use myvar
    myvar.property = ...
}
else
{
    // Whoops! myvar is null and cannot be used without first
    // assigning it to an instance reference
    // Attempting to use myvar here will result in NullReferenceException
}

其它参考5


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



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

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


其它参考6


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

其它参考7


如果您尚未初始化引用类型,并且想要设置或读取其中一个属性,则会抛出 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,重要的是要知道值类型和引用类型之间的区别。[[509]] [[510]]


因此,如果您正在处理值类型,则NullReferenceExceptions可以发生。虽然您在处理引用类型时需要保持警惕!


正如名称所暗示的,只有引用类型可以保存引用或从字面上指向任何内容(或null)。值类型始终包含值。


参考类型(必须检查这些类型):



  • 动态

  • 物体

  • 字符串



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



  • 数字类型

  • 积分类型

  • 浮点类型

  • 小数

  • BOOL

  • 用户定义的结构


其它参考8


可能发生NullReferenceExceptions的另一种情况是as运算符的使用(不正确):[[511]]


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是不兼容的类型; a Car无法转换/转换为Book。当此演员表失败时,as返回null。在此之后使用mybook会导致NullReferenceException


一般来说,你应该使用演员表或as,如下:


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


ComicBook cb = (ComicBook)specificBook;


如果您不确定该类型,但是您想尝试将其用作特定类型,请使用as:


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

其它参考9


您正在使用包含空值引用的对象。所以它给出了一个null异常。在这个例子中,字符串值为null,当检查它的长度时,发生了异常。


例:


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


异常错误是:



  未处理的异常:

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


其它参考10


Simon Mourier举了这个例子:[[512]]


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


取消装箱 转换(演员)来自 object(或其中一个类System.ValueTypeSystem.Enum]],或从接口类型)到值类型(Nullable<>除外)本身给出NullReferenceException


在另一个方向,来自 a Nullable<> 拳击 转换,HasValue等于false 到一个引用类型,可以给出一个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


虽然什么导致NullReferenceExceptions和避免/修复的方法这样的异常已在其他答案中得到解决,但许多程序员还没有学到如何独立在开发期间调试此类异常。[[513]]


在Visual Studio中,由于Visual Studio调试器,这通常很容易。[[514]]





首先,确保将捕获正确的错误 - 请参阅
如何在VS2010中允许打破'System.NullReferenceException'? 1 [[515]]


然后启动调试(F5)或将[[VS调试器]]连接到正在运行的进程。有时使用Debugger.Break可能会有用,它会提示启动调试器。[[516]] [[517]] [[518]]


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


例如,
在以下行中,可以导致异常的唯一代码是if myString求值为null。这可以通过查看监视窗口或在立即窗口中运行表达式来验证。[[519]] [[520]]


var x = myString.Trim();


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


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


一旦其中找到了抛出异常,通常会向后推理以找出[[错误地]]引入空值的位置 - 通常是微不足道的 -


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





1 如果Break of Throws过于激进并且调试器在.NET或第三方库中的NPE上停止,则可以使用Break on User-Unhandled来限制捕获的异常。此外,VS2012还推出了Just My Code,我也推荐使用。[[522]] [[523]]



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


其它参考12


当实体框架中使用的实体的类名与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;}
}


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


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


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


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


有关讨论,请查看DbContext.saveChanges()[[524]]中的NullReferenceException。

其它参考13


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


有关详细示例,请参阅测试自定义AuthorizationAttribute时抛出的NullReferenceException。[[525]]

其它参考14


我有一个不同的观点来回答这个问题。这种答案我还能做些什么来避免它?


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

其它参考15


关于我该怎么办的问题,可以有很多答案。


在开发时,一种更正式的方法可以防止在合同中应用设计。这意味着您需要在开发时在系统上设置类不变量和/或甚至函数/方法前提条件和后置条件。[[526]]]]


简而言之, class invariants 确保您的类中存在一些在正常使用中不会被违反的约束(因此,类不会处于不一致状态)。 前置条件意味着作为函数/方法的输入提供的数据必须遵循一些约束设置并且从不违反它们,并且后置条件意味着函数/方法输出必须再次遵循设置约束而不会违反它们。
在执行无错程序时,合同条件永远不会违反,因此在调试模式下实际检查合同设计,同时在发行版中禁用,以最大化开发了系统性能。


这样,您可以避免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 项目。[[527]]


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


更新:值得一提的是,该术语是Bertrand Meyer在设计Eiffel编程语言时所创造的。[[529]]

其它参考16


当我们尝试访问null对象的Properties或者字符串值变为空并且我们尝试访问字符串方法时,抛出NullReferenceException


例如:



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


    string str = string.Empty;
    str.ToLower(); // throw null reference exception
    

  2. 访问null对象的属性时:


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


其它参考17


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中是空的。直到我将其更改为:


@{
    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误读了&符号和括号。

其它参考18


你能做些什么?


这里有很多很好的答案,解释了什么是空引用以及如何调试它。但是如何防止这个问题或者至少让它更容易被捕获的问题很少。


检查参数


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


ArgumentNullException的构造函数甚至将参数的名称和消息作为参数,因此您可以准确地告诉开发人员问题是什么。


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


使用工具


还有几个库可以提供帮助。例如,Resharper可以在编写代码时为您提供警告,尤其是在您使用其属性时:NotNullAttribute [[530]]


有Microsoft Code Contracts,你使用类似Contract.Requires(obj != null)的语法,它为你提供运行时和编译检查:代码合同介绍。[[531]]


还有PostSharp,它允许你只使用这样的属性:


public void DoSometing([NotNull] obj)


通过这样做,并使构建过程obj的PostSharp部分在运行时检查为null。请参阅:PostSharp null check [[532]]


普通代码解决方案


或者,您始终可以使用普通旧代码编写自己的方法。例如,这里有一个可用于捕获空引用的结构。它的模型与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);
}


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


Person person = GetPerson();

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


或者你甚至可以在方法只是通过执行转换返回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[[533]]


相关语言功能


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


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


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


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


这是一个很棒的功能,但是它可以为你提供更少的信息。它并没有明确表明4中的哪一个是空的。


内置如Nullable?


C#有一个很好的Nullable<T>速记,你可以通过在类型之后加一个问号来使某些东西可以为空int?


如果C#有类似上面NotNull<T>结构的东西并且有类似的简写,也许是感叹号(!),那么你可以编写如下内容:public void WriteName(Person! person)

其它参考19


您可以使用c#6中的Null条件运算符以干净的方式修复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的值。


更多细节:空条件运算符[[534]]

其它参考20


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


例如:假设你有一个名为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的引用已声明但未实例化,或者对象的实例未分配给该类的引用。


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


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

其它参考21


有趣的是,这个页面上没有一个答案提到了两个边缘情况,希望没有人介意我添加它们:


边缘情况#1:并发访问字典



.NET中的通用字典不是线程安全的,当您尝试从两个并发访问密钥时,它们有时可能会抛出NullReference或甚至(更频繁)KeyNotFoundException线程。在这种情况下,例外是非常误导的。


边缘情况#2:不安全代码



如果unsafe代码抛出NullReferenceException,您可能会查看指针变量,并检查IntPtr.Zero或其他内容。这是同样的事情(空指针异常),但在不安全的代码中,变量通常被强制转换为值类型/数组等,并且你会撞到墙上,想知道值类型如何抛出这个例外。


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

其它参考22


当您尝试使用的类的对象未实例化时,会发生未设置为对象实例的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类型的变量,请注意此时未实例化Student类。
因此,当语句 s.GetFullName()被执行时,它将抛出NullReferenceException。

其它参考23


好吧,简单来说:


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


那么如何解决这个问题:



  1. 调试并让调试器中断...它将直接将您带到已损坏的变量...现在您的任务是简单地修复此问题。使用相应的 new 关键字地点。

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


    if (i == null) {
        // Handle this
    }
    

  3. 最难的......如果 GC 已经收集了对象...如果你试图使用字符串找到一个对象,通常会发生这种情况......也就是说,通过名字找到它对象然后可能会发生GC可能已经清理它...这很难找到并且将成为一个很大的问题...解决这个问题的一个更好的方法是在开发过程中进行必要的空检查。这将为您节省大量时间。[[535]]



通过名称查找我的意思是一些框架允许你使用字符串FIndObjects,代码可能如下所示: FindObject(ObjectName);

其它参考24


如果我们考虑可以抛出此异常的常见方案,则在顶部访问具有对象的属性。


例如:


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


在这里,如果address为null,那么你将得到NullReferenceException。


因此,作为一种实践,我们应该始终使用空检查,然后才能访问此类对象中的属性(特别是在通用中)


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

其它参考25


引用类型默认为 null ,表示它们没有引用任何对象。因此,如果您尝试访问正在引用的对象并且没有一个,那么您将获得 NullReferenceException


对于Ex:


SqlConnection connection = null;
connection.Open();


运行此代码时,您将获得:


System.NullReferenceException: Object reference not set to an instance of an object.


您可以通过以下编码来避免此错误:


if (connection != null){
    connection.Open();
}


注意:为了避免此错误,您应该在尝试对它们执行任何操作之前始终初始化对象。

其它参考26


如果在保存或编译构建期间收到此消息,只需关闭所有文件,然后打开任何文件进行编译和保存。


对我来说,原因是我重命名了文件,旧文件仍然打开。

其它参考27


从字面上看,修复NullReferenceExeption最简单的方法有两种。
如果您有一个附加脚本的GameObject和一个名为rb(rigidbody)的变量,那么当您开始游戏时,此变量将从null开始。

这就是您获得NullReferenceExeption的原因,因为计算机没有存储在该变量中的数据。


我将使用RigidBody变量作为示例。

我们可以通过以下几种方式实际上轻松添加数据:



  1. 使用AddComponent> Physics> Rigidbody
    将RigidBody添加到对象
    然后进入你的脚本并输入rb = GetComponent<Rigidbody>();

    这行代码在Start()Awake()函数下效果最佳。

  2. 您可以通过编程方式添加组件,并使用一行代码同时分配变量:rb = AddComponent<RigidBody>();



进一步说明:如果你想让Unity为你的对象添加一个组件而你可能忘记添加一个组件,你可以在你的类声明上面输入[RequireComponent(typeof(RigidBody))](所有使用中的空间)。

享受并享受制作游戏的乐趣!

其它参考28


这意味着您正在尝试操作具有引用但尚未初始化的内容
点击
这里要做的第一件事是检查创建的每个实例。
点击
点击
使用断点,监视,检查您的varibale值。
点击
跟踪堆栈跟踪并搜索确实存在问题的确切行和列

其它参考29


要使用对象的方法和成员,首先必须创建该对象。如果你没有创建它(应该保持该对象的变量没有被初始化),但是你试图使用它的方法或变量你会得到那个错误。


有时你可能忘了做初始化。


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