什么是NullReferenceException,如何解决?


1875

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

你调用的对象是空的。

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


VS 2017中的异常帮助程序在诊断此异常的原因方面会更加有用- 在New Exception Helper下的blogs.msdn.microsoft.com/visualstudio/2016/11/28/…。
— Zev Spitz

亲爱的未来访客,这个问题的答案同样适用于ArgumentNullException。如果您的问题已作为该问题的重复部分被关闭,并且您遇到了ANE,请按照答案中的说明进行调试和解决问题。

@will ANE仅在将null作为参数传递时才会发生。如果ANE问题作为该问题的副本而关闭,您能举个例子吗?
— 约翰·桑德斯

它出现在Meta上,但是我必须去挖掘链接。但是关于该注释,ANE只是一个NRE,但是有人添加了抢先检查,并且您至少确切地知道什么为空(提供了参数名称),因此,比起一个简单的NRE,它的诊断要容易一些。

Answers:


2415

原因是什么?

底线

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

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

null 可以具有不同的含义:

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

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

进一步来说

该runtime扔NullReferenceException 总是意味着同样的事情:你要使用的引用,引用未初始化(或它一次初始化,但不再初始化)。

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

string foo = null;
foo.ToUpper();

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

调试

您如何找到来源NullReferenceException?除了查看将要引发的异常本身之外,Visual Studio中的常规调试规则也适用:放置战略断点并检查变量(通过将鼠标悬停在其名称上,然后打开(快速)监视窗口或使用各种调试面板(例如本地和自动)。

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

通过以这种方式遵循程序流程,您可以找到实例不应为null的位置以及未正确设置实例的原因。

例子

可能引发异常的一些常见方案:

泛型

ref1.ref2.ref3.member

如果ref1或ref2或ref3为空,则将获得一个NullReferenceException。如果要解决此问题,请通过将表达式重写为更简单的等价项来找出哪个为空:

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

具体来说,在中HttpContext.Current.User.Identity.Name,HttpContext.Current可以为null,或者User属性可以为null,或者Identity属性可以为null。

间接

public class Person 
{
    public int Age { get; set; }
}
public class Book 
{
    public Person Author { get; set; }
}
public class Example 
{
    public void Foo() 
    {
        Book b1 = new Book();
        int authorAge = b1.Author.Age; // You never initialized the Author property.
                                       // there is no Person to get an Age from.
    }
}

如果要避免子(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; }
}

嵌套集合的Initializers行为相同:

Person p1 = new Person 
{
    Books = {
         new Book { Title = "Title1" },
         new Book { Title = "Title2" },
    }
};

这转化为

Person p1 = new Person();
p1.Books.Add(new Book { Title = "Title1" });
p1.Books.Add(new Book { Title = "Title2" });

该new Person只创建一个实例Person,但Books集合仍然是null。集合Initializer语法不会为创建一个集合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
    }
}

###Bad Naming Conventions:

If you named fields differently from locals, you might have realized that you never initialized the field. 

公共类Form1 {私人客户客户;

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空视图模型

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

// 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带有事件处理程序等的早期创建的控件,将引发A ,在事件InitializeComponent引用期间将触发A。

例如 :

<Grid>
    <!-- Combobox declared first -->
    <ComboBox Name="comboBox1" 
              Margin="10"
              SelectedIndex="0" 
              SelectionChanged="comboBox1_SelectionChanged">
       <ComboBoxItem Content="Item 1" />
       <ComboBoxItem Content="Item 2" />
       <ComboBoxItem Content="Item 3" />
    </ComboBox>

    <!-- Label declared later -->
    <Label Name="label1" 
           Content="Label"
           Margin="10" />
</Grid>

在这里comboBox1创建之前label1。如果comboBox1_SelectionChanged尝试引用`label1,则尚未创建。

private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    label1.Content = comboBox1.SelectedIndex.ToString(); // NullReference here!!
}

更改中的声明顺序XAML(即,label1之前列出comboBox1,而忽略设计哲学问题)至少可以解决NullReferenceException此处的问题。

搭配 as

var myThing = someObject as Thing;

当强制转换失败(并且本身为null)时,此方法不会抛出an,InvalidCastException但会返回a 。因此请注意。nullsomeObject

LINQ FirstOrDefault()和SingleOrDefault()

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

前言

foreach尝试迭代null集合时引发。通常由null返回集合的方法的意外结果引起。

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

更实际的示例-从XML文档中选择节点。如果未找到节点,但初始调试显示所有属性均有效,则会抛出该异常:

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

避免方法

显式检查null并忽略空值。

如果您期望引用有时为空,则可以null在访问实例成员之前检查引用是否为空:

void PrintName(Person p)
{
    if (p != null) 
    {
        Console.WriteLine(p.Name);
    }
}

明确检查null并提供默认值。

您期望返回实例的方法调用可以返回null,例如,在找不到要查找的对象时。在这种情况下,您可以选择返回默认值:

string GetCategory(Book b) 
{
    if (b == null)
        return "Unknown";
    return b.Category;
}

显式检查null方法调用并引发自定义异常。

您还可以抛出一个自定义异常,仅在调用代码中将其捕获:

string GetCategory(string bookTitle) 
{
    var book = library.FindBook(bookTitle);  // This may return null
    if (book == null)
        throw new BookNotFoundException(bookTitle);  // Your custom exception
    return book.Category;
}

Debug.Assert如果值永远不应为null,则使用该值可以在异常发生之前更早地发现问题。

当您在开发过程中知道某个方法可以但不能返回时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.
}

尽管此检查将不会在您的发行版本中结束,但会导致它在发行版模式下运行时NullReferenceException再次引发book == null。

使用GetValueOrDefault()的nullable值类型提供一个默认值,当他们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遇到a时提供默认值的简写:

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,ToUpper如果person.Title为,则不会调用null。

当然,您仍然必须检查titlenull或将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<sup>th</sup>元素。

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

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

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

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

使用与可为空的值类型相同的语法来记录可为空的引用类型:将a ?附加到变量的类型。

用于调试和修复迭代器中的空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结果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调用when时,将立即GetFrobsForReal执行空检查,然后在序列迭代时执行。

如果您检查LINQ对象的参考源,您会发现整个使用了这种技术。编写起来有点笨拙,但是它使调试null错误更加容易。 优化代码以方便调用者,而不是作者。

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

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

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

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

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

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

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

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

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

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

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


55
也许这是一个愚蠢的评论,但是避免此问题的第一个也是最好的方法是初始化对象吗?对我来说,如果发生此错误,通常是因为我忘了初始化数组元素之类的东西。我认为将对象定义为null然后再引用它的情况要少得多。也许可以给出解决描述旁边的每个问题的方法。还是个好帖子。
— JPK

30
如果没有对象,而是方法或属性的返回值怎么办?
— 约翰·桑德斯

6
这本书/作者的例子有点怪异。智能感知甚至如何工作?这是什么,我对计算机不好用……

5
@Will:我的最后编辑有帮助吗?如果不是,请更明确地说明您所认为的问题。
— 约翰·桑德斯

6
@JohnSaunders哦,不,对不起,我的意思是该对象的初始化程序版本。 new Book { Author = { Age = 45 } };内部初始化如何实现……我想不出内部init会起作用,但它可以编译和智能感知的情况……除非针对结构?

311

NullReference异常— Visual Basic

在NullReference Exception为Visual Basic中是从一个在任何不同的C# 。毕竟,它们都报告了它们都使用的.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。这就是您想要的,因为它将识别出问题的确切位置,并允许您识别导致问题的对象。

MsgBox捕获中显示的A Error while...几乎没有帮助。这种方法还会导致非常糟糕的堆栈溢出问题,因为您无法描述实际的异常,所涉及的对象甚至发生异常的代码行。

您还可以使用Locals Window(Debug-> Windows-> 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。

NewNullReference Exceptions在所检查的堆栈溢出问题中缺少操作员是第一位原因。

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

明确地说,Dim(或Private)仅声明一个变量及其Type。变量的作用域 -是否在整个模块/类中存在,还是过程的局部变量-由声明位置确定。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}

数据类型和数组大小是从分配的数据推断出来的。类/模块级别的声明仍然需要As <Type>使用Option Strict:

Private myDoubles As Double() = {1.5, 2, 9.9, 18, 3.14}

示例:类对象数组

Dim arrFoo(5) As Foo

For i As Integer = 0 To arrFoo.Count - 1
   arrFoo(i).Bar = i * 10       ' Exception
Next

数组已创建,但其中的Foo对象尚未创建。

补救

For i As Integer = 0 To arrFoo.Count - 1
    arrFoo(i) = New Foo()         ' Create Foo instance
    arrFoo(i).Bar = i * 10
Next

使用a List(Of T)将使没有有效对象的元素变得非常困难:

Dim FooList As New List(Of Foo)     ' List created, but it is empty
Dim f As Foo                        ' Temporary variable for the loop

For i As Integer = 0 To 5
    f = New Foo()                    ' Foo instance created
    f.Bar =  i * 10
    FooList.Add(f)                   ' Foo object added to list
Next

有关更多信息,请参见:


清单和收藏

.NET集合(种类繁多-列表,字典等)也必须实例化或创建。

Private myList As List(Of String)
..
myList.Add("ziggy")           ' NullReference

由于相同的原因,您将获得相同的异常- myList仅被声明,但未创建实例。补救措施是相同的:

myList = New List(Of String)

' Or create an instance when declared:
Private myList As New List(Of String)

常见的疏忽是使用集合的类Type:

Public Class Foo
    Private barList As List(Of Bar)

    Friend Function BarCount As Integer
        Return barList.Count
    End Function

    Friend Sub AddItem(newBar As Bar)
        If barList.Contains(newBar) = False Then
            barList.Add(newBar)
        End If
    End Function

这两个过程都将导致NRE,因为barList它仅被声明而不被实例化。创建的实例Foo也不会创建internal的实例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)Class。


数据提供者对象

与数据库礼物的NullReference工作的机会,因为可以有很多的对象(Command,Connection,Transaction,Dataset,DataTable,DataRows在一次使用中....)。 注意:使用哪个数据提供程序(MySQL,SQL Server,OleDB等)无关紧要,概念是相同的。

例子1

Dim da As OleDbDataAdapter
Dim ds As DataSet
Dim MaxRows As Integer

con.Open()
Dim sql = "SELECT * FROM tblfoobar_List"
da = New OleDbDataAdapter(sql, con)
da.Fill(ds, "foobar")
con.Close()

MaxRows = ds.Tables("foobar").Rows.Count      ' Error

和以前一样,ds声明了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)

错字是这里的问题:Employeesvs Employee。没有DataTable创建名为“ Employee”的名称,因此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
   ...

该代码只是测试Items同时兼具myFoo并Bar也可能什么都不是。的补救是在一个时间测试整个链或对象的一个的路径:

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

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

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

不可能引用null对象“下游”的任何内容。这也适用于控件:

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

在这里,myWebBrowser或Document可能为Nothing或formfld1元素可能不存在。


UI控件

Dim cmd5 As New SqlCommand("select Cartons, Pieces, Foobar " _
     & "FROM Invoice where invoice_no = '" & _
     Me.ComboBox5.SelectedItem.ToString.Trim & "' And category = '" & _
     Me.ListBox1.SelectedItem.ToString.Trim & "' And item_name = '" & _
     Me.ComboBox2.SelectedValue.ToString.Trim & "' And expiry_date = '" & _
     Me.expiry.Text & "'", con)

除其他外,此代码无法预期用户可能未在一个或多个UI控件中选择某些内容。 ListBox1.SelectedItem可能是Nothing,因此ListBox1.SelectedItem.ToString将导致NRE。

补救

使用数据之前先验证数据(还要使用Option Strict和SQL参数):

Dim expiry As DateTime         ' for text date validation
If (ComboBox5.SelectedItems.Count > 0) AndAlso
    (ListBox1.SelectedItems.Count > 0) AndAlso
    (ComboBox2.SelectedItems.Count > 0) AndAlso
    (DateTime.TryParse(expiry.Text, expiry) Then

    '... do stuff
Else
    MessageBox.Show(...error message...)
End If

或者,您可以使用 (ComboBox5.SelectedItem IsNot Nothing) AndAlso...


Visual Basic表单

Public Class Form1

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

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

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

这是获得NRE的相当普遍的方法。在C#中,根据其编码方式,IDE将报告Controls当前上下文中不存在的内容,或“无法引用非静态成员”。因此,在某种程度上,这是仅VB的情况。这也很复杂,因为它可能导致失败的级联。

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

  • 列表和集合将为空
  • 数组将包含五个元素
  • 该somevar分配将导致立即NRE因为没有什么不具有.Text财产

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

由于NRE之后,您的Sub New或Form Load事件中不会再有其他代码运行,因此许多其他事情可以保留未初始化。

Sub Form_Load(..._
   '...
   Dim name As String = NameBoxes(2).Text        ' NRE
   ' ...
   ' More code (which will likely not be executed)
   ' ...
End Sub

请注意,这适用于所有和所有控件和组件引用,这些引用使它们在以下位置非法:

Public Class Form1

    Private myFiles() As String = Me.OpenFileDialog1.FileName & ...
    Private dbcon As String = OpenFileDialog1.FileName & ";Jet Oledb..."
    Private studentName As String = TextBox13.Text

部分补救

奇怪的是,VB没有提供警告,但补救措施是在窗体级别声明容器,但在控件确实存在时在窗体加载事件处理程序中初始化它们。只要您的代码在调用之后,就可以完成此操作:Sub NewInitializeComponent

' Module level declaration
Private NameBoxes as TextBox()
Private studentName As String

' Form Load, Form Shown or Sub New:
'
' Using the OP's approach (illegal using OPTION STRICT)
NameBoxes = New TextBox() {Me.Controls("TextBox1"), Me.Controls("TestBox2"), ...)
studentName = TextBox32.Text           ' For simple control references

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

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

“ Button2”位于 Panel

补救

而不是使用表单的Controls集合通过名称进行间接引用,而应使用控件引用:

' Declaration
Private NameBoxes As TextBox()

' Initialization -  simple and easy to read, hard to botch:
NameBoxes = New TextBox() {TextBox1, TextBox2, ...)

' Initialize a List
NamesList = New List(Of TextBox)({TextBox1, TextBox2, TextBox3...})
' or
NamesList = New List(Of TextBox)
NamesList.AddRange({TextBox1, TextBox2, TextBox3...})

函数什么都不返回

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但这不能解决问题。任何尝试使用when的返回值someCondition = False都会导致NRE:

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

补救

用替换Exit Function功能Return bList。返回空 List与returning是不同的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

这是一种未按预期创建对象的情况,但也证明了empty的反作用Catch。

SQL中有一个多余的逗号(在“ mailaddress”之后),导致处出现异常.ExecuteReader。在Catch不Finally执行任何操作之后,尝试执行清理,但是由于您不能Close为空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

如果找不到CheckBoxwith 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"

如果dgvBooks具有has AutoGenerateColumns = True,它将创建列,但不会命名它们,因此当按名称引用它们时,上面的代码将失败。

补救

手动命名列,或通过索引引用:

dgvBooks.Columns(0).Visible = True

示例2 —提防NewRow

xlWorkSheet = xlWorkBook.Sheets("sheet1")

For i = 0 To myDGV.RowCount - 1
    For j = 0 To myDGV.ColumnCount - 1
        For k As Integer = 1 To myDGV.Columns.Count
            xlWorkSheet.Cells(1, k) = myDGV.Columns(k - 1).HeaderText
            xlWorkSheet.Cells(i + 2, j + 1) = myDGV(j, i).Value.ToString()
        Next
    Next
Next

当你DataGridView拥有AllowUserToAddRows为True(默认值),则Cells底部的空白/新行会都含有Nothing。使用内容的大多数尝试(例如,ToString)将导致NRE。

补救

使用For/Each循环并测试该IsNewRow属性以确定它是否是最后一行。这AllowUserToAddRows是正确的还是无效的:

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

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


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异常


226

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

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

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

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

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

在这里,SelectedDate其实是一个性质-的DateTime类型-的的CalendarWeb控件类型,结合可完全恢复一些空。隐式ASP.NET生成器将创建一段与上面的强制转换代码等效的代码。这将引起一个NullReferenceException很难发现的问题,因为它位于ASP.NET生成的代码中,可以很好地进行编译...


7
很棒的收获。一种避免的方式:DateTime x = (DateTime) o as DateTime? ?? defaultValue;
— Serge Shultz 2015年

159

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

SqlConnection connection = null;
connection.Open();

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

为避免此错误:

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

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


3
JetBrains的Resharper工具将识别代码中可能存在空引用错误的每个位置。这是不正确的。我有一个没有检测到的解决方案,但是代码偶尔会导致异常。我怀疑当涉及多线程时,偶尔(至少是至少)他们无法检测到,但是由于无法确定错误的位置,所以我无法进一步评论。
— j riv

但是,当NullReferenceException出现在有用的HttpContext.Current.Responce.Clear()中时,如何解决此问题。上述任何解决方案都无法解决。因为在创建的HttpContext其目标对象,然后一个错误出现“重载决策失败,因为没有可访问的‘新’接受这个自变量数。
— 阳光桑迪普

157

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

96

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

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

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


87

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


87

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

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


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

例:

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

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

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

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

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

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

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

  • 动态
  • 宾语
  • 串

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

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

6
-1:由于问题是“什么是NullReferenceException”,因此值类型无关。
— 约翰·桑德斯

21
@约翰·桑德斯:我不同意。作为软件开发人员,能够区分值和引用类型真的很重要。否则人们最终将检查整数是否为null。
— Fabian Bigler

5
是的,只是不在此问题中。
— 约翰·桑德斯

4
感谢您的提示。我对其进行了一些改进,并在顶部添加了一个示例。我仍然认为提及引用和值类型很有用。
— Fabian Bigler

5
我认为您没有添加其他答案中未包含的任何内容,因为该问题预先假定了引用类型。
— 约翰·桑德斯

78

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是不兼容的类型; 一个Car不能被转换/施放到一个Book。如果此转换失败,则as返回null。mybook在此之后使用会导致NullReferenceException。

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

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

ComicBook cb = (ComicBook)specificBook;

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

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

2
取消装箱变量时,这可能会发生很多。我发现它在更改UI元素的类型后经常在事件处理程序中发生,但忘记更新后台代码。
— 布伦丹

65

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

例:

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

异常错误是:

未处理的异常:

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


1
多么深刻!我从未考虑过'null'常数是参考值。这就是C#如何抽象“ NullPointer”吧?正如我在C ++中所回忆的那样,B / c可以通过取消引用未初始化的指针(即c#中的ref类型)来导致NPE,该指针的默认值恰好是未分配给该进程的地址(许多情况下,该值为0,特别是在执行自动初始化的更高版本的C ++中,该版本属于OS-带有f并且会死掉beeotch(或者只是抓住了OS攻击您的进程所使用的信号)。
— samis

64

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

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


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

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

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

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

var x = myString.Trim();

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

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

一旦找到了引发异常的位置,通常不容易进行倒推以找出[错误地]引入了null值的位置-

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


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

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


59

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

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

从(或从一个类或,或从接口类型)到值类型(非)的拆箱转换(广播)本身给出。 objectSystem.ValueTypeSystem.EnumNullable<>NullReferenceException

在另一个方向上,一拳击转换从一个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<>实例进行装箱时使用的特殊规则。


42

添加一种情况,即实体框架中使用的实体的类名与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



40

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

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

6
-1:这仅处理单个方案-未初始化的依赖项。这是NullReferenceException的少数情况。大多数情况是对对象如何工作的简单误解。接下来最常见的情况是开发人员认为该对象将自动初始化的其他情况。
— 约翰·桑德斯

为了避免NullReferenceException,通常不使用依赖项注入。我认为您在这里找不到一般情况。无论如何,如果您将答案编辑为更多的stackoverflow.com/a/15232518/76337样式,则我将删除下注。
— 约翰·桑德斯

38

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

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

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

这样,您可以避免NullReferenceException由于违反约束集而导致的情况。例如,如果您X在类中使用对象属性,然后稍后尝试调用其方法之一并X具有null值,则将导致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编程语言设计相关的。


2
我想补充一下这一点,因为没有人提到这一点,而就其存在而言,我的目的是丰富这个话题。
— Nick Louloudakis 2014年

2
感谢您丰富主题。我已同意你的加入。现在其他人也可以这样做。
— 约翰·桑德斯

2
鉴于这是一个受到高度重视的话题,所以我认为这是对该主题的一个有价值的补充。我以前听说过代码合同,这是考虑使用它们的一个很好的提醒。
— VoteCoffee 2015年

36

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

例如:

  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 

2
这是不正确的。String.Empty.ToLower()不会引发空引用异常。它代表一个实际的字符串,尽管是一个空字符串(即"")。由于有一个要调用的对象ToLower(),因此在此处抛出空引用异常是没有意义的。
— Kjartan

31

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误读了“&”号和括号。


你想Html.Partial,没有@Html.Partial
— 约翰·桑德斯

另外,请显示引发异常的行以及原因。
— 约翰·桑德斯

在MyOtherView.cshtml,我在这里没有包括发生的错误,因为该模型没有被正确的(这是送Null),所以我知道错误是与我是如何在发送模式。
— 特拉维斯Heeter

22

你能为这个做什么?

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

检查参数

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

ArgumentNullException偶数的构造函数将参数的名称和一条消息作为参数,以便您可以确切地告诉开发者问题出在哪里。

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

使用工具

也有几个库可以提供帮助。例如,“ Resharper”可以在编写代码时为您提供警告,尤其是在使用其属性的情况下:NotNullAttribute

在“ Microsoft Code Contracts”中,您可以使用类似语法的语法Contract.Requires(obj != null),它可以为您提供运行时和编译检查:Code Contracts简介。

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

public void DoSometing([NotNull] obj)

这样,obj将在运行时检查PostSharp是否为构建过程的一部分。请参阅: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" };
}

或者,当方法只是通过强制转换返回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等属性的类型的对象。如果country,State,County,或者City是null那么address will be空. Therefore you only have to check whether地址isnull`。

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

像Nullable一样内置?

C#有一个很好的速记方式Nullable<T>,您可以通过在这样的类型后面加上一个问号使某些内容为空int?。

如果C#具有NotNull<T>上面的struct之类的东西并且具有类似的速记(也许是感叹号(!)),那么您会写成类似以下内容将是很好的public void WriteName(Person! person)。


2
永不引发NullReferenceException
— John Saunders

@JohnSaunders敢问为什么?(虽然是为什么会这样?)
— 路易斯·佩雷斯

2
NullReferenceException是由CLR引发的。这意味着已发生对空值的引用。这并不意味着对空值的引用会发生,除非您先进行了巧妙的检查。
— 约翰·桑德斯

我明白您的看法,这将使您感到困惑。对于本示例,我已将其更新为常规异常,并在GitHub中将其更新为自定义异常。
— 路易斯·佩雷斯

这样一个基本问题的好答案。当您的代码失败时,还算不错。当它来自您所依赖的某个商业第三方库的内部时,这太可怕了,并且客户支持人员始终坚持认为,必须由您的代码来引起问题。而且您不能完全确定不是,整个项目都将暂停。.实际上,我认为这可能对我的墓碑来说是一个恰当的墓志铭:“对象引用未设置为对象的实例。”
— 李达瑞(Darrel Lee)

10

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

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

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

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

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

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


5
您的字典示例不是一个极端情况。如果对象不是线程安全的,则在多个线程中使用它会产生随机结果。您的不安全代码示例与null哪种方法不同?
— 约翰·桑德斯

10

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

在执行成员访问(?。)或索引(?[)操作之前,它用于测试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的值。

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


9

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

例如:假设您有一个名为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;  
     }
}

4

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

假设您有一个名为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。


3

好吧,简单来说:

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

那么如何解决这个问题:

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

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

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

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


3
如果您引用了某个对象,则GC永远不会对其进行清理
— John Saunders

2
如果您使用诸如FindObject(“ Object Name”)之类的东西,GC将无法事先知道您将要引用该对象..这就是试图解释的原因..这些发生在运行时
— Akash Gutha

2
有一些框架可以在C#中提供此功能,例如Unity。该问题与BCl没有任何关系。在批评之前,先搜索一下互联网,这里有大量的功能,对于您的信息,我什至每天都会使用。现在,请告诉我答案如何没有意义。
— Akash Gutha 2015年

2
docs.unity3d.com/ScriptReference/… 检查链接并更正自己mr.expert:p
— Akash Gutha 2015年

我在链接中看到的示例将GameObject.Find的结果分配给一个成员字段。这是一个引用,并且直到收集到包含对象为止,GC才会收集它。
— 约翰·桑德斯

1

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

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

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

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


-1

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

例如:

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

-3

这基本上是一个Null引用异常。作为微软明─

当您尝试访问其值为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()之类的系统方法检查是否为空,如果有空值,则分配默认值。

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

希望这也能有所帮助!


您基本上复制了该博客文章的一半,并且没有添加现有答案无法解决的新内容。
— CodeCaster

@codecaster是指您从自己的博客重写摘要时表示正在复制。我知道我的答案没有新内容,以前的答案没有新内容,但是我希望以更复杂的方式做出贡献,并让其他人理解我的理解方式。即使会帮助一个人也会很高兴。真诚。
— Wasim

-4

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.