问题

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

Object reference not set to an instance of an object.

这是什么意思,我该怎么办呢?



解决方法

What is the cause?

Bottom Line

您尝试在VB.NET中使用 null (或 Nothing ).这意味着您可以将其设置为 null ,或者从不将其设置为任何东西.

像任何东西一样, null 被传递.如果方法"A"中的 null ,则方法"B"可以将 null 方法"A".

本文的其余部分将详细介绍,并显示许多程序员经常犯的错误,可能导致一个 NullReferenceException .

More Specifically

运行时抛出 NullReferenceException 总是意味着同样的事情:你正试图使用​​引用.引用未初始化(或已初始化,但未初始化更长的时间).

这意味着引用是 null ,并且您无法通过 null 引用访问成员.最简单的情况:

string foo = null;
foo.ToUpper();

这将在第二行抛出一个 NullReferenceException ,因为你不能调用 string 引用的实例方法 ToUpper指向 null .

Debugging

如何找到 NullReferenceException 的来源?除了查看异常本身,它将被精确地抛出它发生的位置,在Visual Studio中的一般调试规则适用:地方战略断点和检查您的变量,方法是将鼠标悬停在其名称上,打开(快速)观察窗口或使用各种调试面板(如本地和自动).

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

按照程序流程,这样你可以找到实例不应该为null的位置,以及为什么它没有正确设置.

Examples

可抛出异常的一些常见情况:

Generic

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 为空,或 Identity 属性可以为空.

Indirect

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

虽然使用了 new 关键字,但它只创建一个 Book 的新实例,而不是 Person 的一个新实例,因此作者的属性仍然是 null .

Array

int[] numbers = null;
int n = numbers[0]; // numbers is null. There is no array to index.

Array Elements

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.

Collection/List/Dictionary

Dictionary<string, int> agesForNames = null;
int age = agesForNames["Bob"]; // agesForNames is null.
                               // There is no Dictionary to perform the lookup.

Range Variable (Indirect/Deferred)

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.

Events

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:

如果您将字段命名为与本地字段不同,您可能已经意识到您从未初始化字段.

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 Page Life cycle:

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 Session Values

// 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 empty view models

如果在ASP.NET MVC视图中引用 @Model 的属性时发生异常,您需要了解在您的操作方法中设置 Model 当你返回视图.当您从控制器返回空模型(或模型属性)时,当视图访问它时会发生异常:

// Controller
public class Restaurant:Controller
{
    public ActionResult Search()
    {
         return View();  // Forgot the provide a Model here.
    }
}

// Razor view 
@foreach (var restaurantSearch in Model.RestaurantSearch)  // Throws.
{
}

<p>@Model.somePropertyName</p> <!-- Also throws -->

WPF Control Creation Order and Events

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>

这里 comboBox1 是在 label1 之前创建的.如果 comboBox1_SelectionChanged 尝试引用 label1 ,它将不会创建.

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

更改XAML中声明的顺序(即:列出 comboBox1 之前的 label1 ,忽略设计哲学问题,至少会解决 NullReferenceException < / code>这里.

Cast with as

var myThing = someObject as Thing;

这不会抛出一个InvalidCastException,但是当转换失败时(当someObject本身为null时)返回 null .所以要意识到这一点.

LINQ FirstOrDefault() and SingleOrDefault()

普通版本 First() Single()在没有任何内容时抛出异常.在这种情况下,"OrDefault"版本返回null.所以要意识到这一点.

foreach

foreach 当你尝试迭代null集合时抛出.通常由来自返回集合的方法的意外 null 结果引起.

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

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

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

Ways to Avoid

Explicitly check for null, and ignore null values.

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

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

Explicitly check for null, and provide a default value.

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

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

Explicitly check for null from method calls and throw a custom exception.

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

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

Use Debug.Assert if a value should never be null, to catch the problem earlier than the exception occurs.

当你知道在开发过程中一个方法可能,但实际上不应该返回 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 .

Use GetValueOrDefault() for nullable value types to provide a default value when they are 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

Use the null coalescing operator: ?? [C#] or 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;
}

Use the null condition operator: ?. (available in C# 6 and VB.NET 14):

这有时也称为安全导航或Elvis(在其形状之后)运算符.如果运算符左侧的表达式为null,则不会评估右侧,而是返回null.这意味着这样的情况:

var title = person.Title.ToUpper();

如果此人没有标题,则会抛出异常,因为它试图在具有空值的属性上调用 ToUpper .

在C#5及以下版本中,可使用以下方法保护:

var title = person.Title == null ? null : person.Title.ToUpper();

现在标题变量将为null,而不是抛出异常. C#6引入了更短的语法:

var title = person.Title?.ToUpper();

这将导致title变量为null,如果 person.Title 为null,则不会调用ToUpper.

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

// regular null check
int titleLength = 0;
if (title != null)
    titleLength = title.Length; // If title is null, this would throw NullReferenceException

// combining the `?` and the `??` operator
int titleLength = title?.Length ?? 0;



相关问题推荐