什么是NullReferenceException,如何解决? - c#

This question's answers are a community effort。编辑现有答案以改善此职位。它目前不接受新的答案或互动。
                            
                        
                    
                
                            
                    
我有一些代码,当它执行时,它抛出一个NullReferenceException,说:

  你调用的对象是空的。

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

c#参考方案

原因是什么?

底线

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

像其他任何东西一样,null也可以通过。如果在方法“ A”中为null,则可能是方法“ B”将null传递给方法“ A”。

null可以具有不同的含义:

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

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

进一步来说

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

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

string foo = null;
foo.ToUpper();

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

调试

您如何找到NullReferenceException的来源?除了查看异常本身(将其准确地抛出在异常发生的位置)之外,还适用Visual Studio中的常规调试规则:放置战略断点和inspect your variables,方法是将鼠标悬停在它们的名称上,然后打开(快速)监视窗口或使用各种调试面板(例如本地和自动)。

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

通过以这种方式遵循程序流程,您可以找到实例不应为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; }
}

嵌套的集合初始值设定项的行为相同:

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的属性时发生异常,则需要了解在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期间触发引用早期创建的控件的情况下引发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>

在此,在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;

当强制转换失败时(并且someObject本身为null),这不会引发InvalidCastException,而是返回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

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

尽管此检查will not end up in your release build,导致在释放模式下运行时NullReferenceException时再次引发book == null

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,如果ToUpperperson.Title,则不会调用null

当然,您仍然必须检查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];并返回第ith个元素。

使用空上下文(在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结果为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的调用仅返回一个对象,该对象在进行迭代时将运行迭代器块。

通过这样编写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。它可以位于最低页面的任何位置,并且会产生此异常。

将python scikit学习模型导出到pmml - python

我想将python scikit-learn模型导出到PMML。哪个python软件包最合适?我阅读了有关Augustus的内容,但是我无法使用scikit-learn模型找到任何示例。 python大神给出的解决方案 SkLearn2PMML是 JPMML-SkLearn命令行应用程序周围的薄包装。有关受支持的Scikit-Learn Estimator和…

在php中单击按钮添加表行并设置字段值 - javascript

我有一个表单,必须在单击按钮时添加表行。这是我必须动态添加的表行:(addProdToGroup.php)<tr style="text-align: center;" id="products"> <td><?php $j ?></td> <td><s…

与Mootools Scriptmanager Ajax Asp.net发生冲突? - c#

我正在尝试不同的方法来使这项工作成功,但是没有成功。我正在尝试将mootools与asp.net应用程序集成。我只想用它为我的网站添加一些效果。我也使用更新面板,scriptmanager,因为不希望有完整的回发。在控制台上引发错误TypeError:clientID.startsWith不是函数,并且在进行更新时brwoser reset...。这是整个代…

利用异步Json请求/方法调用来加快页面加载速度 - javascript

我正在研究由4个信息框组成的数字标牌,按照目前的情况,我向控制器发出1个单个Json请求,以检索将在这些框中显示的数据。 action方法在数据库中查询信息,并调用4个单独的私有方法,提供查询信息以填充要作为json返回到视图的列表。下面提供了示例代码。我的问题是,如何通过控制器侧的多线程或对4个独立操作方法的4个单独的Json请求来异步完成此操作。当前控制…

PHP stringID无法从回显中读入onclick - javascript

我有一个音频播放器,可以在HTML中正常工作,但是当我从PHP回显调用时,似乎没有得到div ID的名称,因此它无法播放我的音频。这是我的代码:// Show audio if ($sObj->get('audio') != null) { $sAudio = $sObj->get('audio'); $a…