什么是 NullReferenceException,我该如何解决?
Posted
技术标签:
【中文标题】什么是 NullReferenceException,我该如何解决?【英文标题】:What is a NullReferenceException, and how do I fix it? 【发布时间】:2021-12-27 21:03:04 【问题描述】:我有一些代码,当它执行时,它会抛出一个NullReferenceException
,说:
对象引用未设置为对象的实例。
这是什么意思,我可以做些什么来解决这个错误?
【问题讨论】:
VS 2017 中的异常帮助器将更有助于诊断此异常的原因 -- New Exception Helper 下的blogs.msdn.microsoft.com/visualstudio/2016/11/28/…。 尊敬的未来访问者,此问题的答案同样适用于 ArgumentNullException。如果您的问题已作为此问题的副本而关闭,并且您遇到了 ANE,请按照答案中的说明进行调试并解决您的问题。 @will ANE 只有在将 null 作为参数传递时才会发生。如果 ANE 问题与此问题重复,您能举个例子吗? 它出现在 Meta 上,但我必须去挖掘链接。但至于该评论,ANE 只是一个 NRE,但有人添加了先发制人的检查,并且您至少确切知道什么是 null(提供了参数名称),因此它比直接 NRE 更容易诊断。 【参考方案1】:是什么原因?
底线
您正在尝试使用 null
(或 VB.NET 中的 Nothing
)。这意味着您要么将其设置为 null
,要么根本不将其设置为任何值。
像其他任何东西一样,null
被传递。如果是null
in 方法“A”,则可能是方法“B”传递了null
to 方法“A”。
null
可以有不同的含义:
-
对象变量未初始化,因此指向任何内容。在这种情况下,如果您访问此类对象的成员,则会导致
NullReferenceException
。
开发人员故意使用null
来表明没有可用的有意义的值。请注意,C# 具有变量可空数据类型的概念(如数据库表可以具有可空字段) - 您可以将null
分配给它们以指示其中没有存储任何值,例如int? a = null;
(这是Nullable<int> a = null;
的快捷方式),其中问号表示允许将null
存储在变量a
中。您可以使用if (a.HasValue) ...
或if (a==null) ...
进行检查。可空变量,如本例中的a
,允许通过a.Value
显式访问值,或者通过a
正常访问。 注意如果a
是null
,则通过a.Value
访问它会抛出InvalidOperationException
而不是NullReferenceException
- 你应该事先进行检查,即如果你有另一个不可为空的变量 int b;
那么你应该做像 if (a.HasValue) b = a.Value;
或更短的 if (a != null) b = a;
这样的赋值。
本文的其余部分更详细地介绍了许多程序员经常犯的可能导致NullReferenceException
的错误。
更具体
runtime
抛出 NullReferenceException
always 表示相同的意思:您正在尝试使用引用,但引用未初始化(或者它是一次 已初始化,但不再已初始化)。
这意味着引用是null
,您不能通过null
引用访问成员(例如方法)。最简单的情况:
string foo = null;
foo.ToUpper();
这将在第二行抛出NullReferenceException
,因为您不能在指向null
的string
引用上调用实例方法ToUpper()
。
调试
您如何找到NullReferenceException
的来源?除了查看异常本身(将在其发生的位置准确抛出)之外,Visual Studio 中的一般调试规则适用:放置战略断点和inspect your variables,或者将鼠标悬停在它们的名称上,打开一个 ( Quick)Watch 窗口或使用各种调试面板,如 Locals 和 Autos。
如果您想找出引用设置或未设置的位置,请右键单击其名称并选择“查找所有引用”。然后,您可以在每个找到的位置放置一个断点,并在附加调试器的情况下运行您的程序。每次调试器在这样的断点处中断时,您都需要确定您是否期望引用为非空,检查变量,并验证它是否在您期望的时候指向一个实例。
按照这样的程序流程,你可以找到实例不应该为空的位置,以及为什么没有正确设置。
示例
可以抛出异常的一些常见场景:
通用
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
可以为空,或者User
属性可以为空,或者Identity
属性可以为空。
间接
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.
事件 (C#)
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
(注意:VB.NET 编译器为事件使用插入空值检查,因此没有必要在 VB.NET 中检查 Nothing
的事件。)
错误的命名约定:
如果您对字段的命名与本地名称不同,您可能已经意识到您从未初始化过该字段。
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 View
中引用@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(); // NullReferenceException here!!
更改XAML
中声明的顺序(即,在comboBox1
之前列出label1
,忽略设计理念问题)至少可以解决NullReferenceException
这里的问题。
使用as
转换
var myThing = someObject as Thing;
这不会抛出InvalidCastException
,而是在转换失败时返回null
(并且当someObject
本身为空时)。所以请注意这一点。
LINQ FirstOrDefault()
和 SingleOrDefault()
普通版本First()
和Single()
在没有任何内容时抛出异常。在这种情况下,“OrDefault”版本返回 null
。所以请注意这一点。
foreach
foreach
在您尝试迭代 null
集合时抛出。通常是由返回集合的方法的意外null
结果引起的。
List<int> list = null;
foreach(var v in list) // NullReferenceException here
更现实的例子 - 从 XML 文档中选择节点。如果未找到节点,但初始调试显示所有属性都有效时会抛出:
foreach (var node in myData.MyXml.DocumentNode.SelectNodes("//Data"))
避免方法
明确检查 null
并忽略 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.
虽然这个检查will not end up in your release build,导致它在运行时book == null
在发布模式下再次抛出NullReferenceException
。
对nullable
值类型使用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.Title
为null
,则不会调用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];
并返回第 ith 元素。
使用空上下文(在 C# 8 中可用):
在C# 8
中引入,空上下文和可空引用类型对变量执行静态分析,并在值可能为null
或已设置为null
时提供编译器警告。可空引用类型允许明确允许类型为 null
。
可以使用csproj
文件中的Nullable
元素为项目设置可为空的注释上下文和可为空的警告上下文。此元素配置编译器如何解释类型的可空性以及生成哪些警告。有效设置为:
enable
:可空注释上下文已启用。可空警告上下文已启用。例如,引用类型的变量(字符串)是不可为空的。所有可空性警告均已启用。
disable
:可空注释上下文已禁用。可空警告上下文已禁用。引用类型的变量是无意识的,就像 C# 的早期版本一样。所有可空性警告均已禁用。
safeonly
:可空注释上下文已启用。可为空的警告上下文是仅安全的。引用类型的变量是不可为空的。所有安全可空性警告均已启用。
warnings
:可空注释上下文已禁用。可空警告上下文已启用。引用类型的变量是不经意的。所有可空性警告均已启用。
safeonlywarnings
:可空注释上下文已禁用。可为空的警告上下文是仅安全的。
引用类型的变量是不经意的。所有安全可空性警告均已启用。
可空引用类型使用与可空值类型相同的语法来注明:?
附加到变量的类型。
调试和修复迭代器中的 null derefs 的特殊技术
C#
支持“迭代器块”(在其他一些流行语言中称为“生成器”)。由于延迟执行,NullReferenceException
在迭代器块中调试可能特别棘手:
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
检查可以防止NullReferenceException
,但是您将NullArgumentException
移动到迭代 点,而不是调用点,这调试起来非常混乱。
正确的解决方法是:
// 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();
也就是说,创建一个具有迭代器块逻辑的私有辅助方法和一个执行null
检查并返回迭代器的公共表面方法。现在当GetFrobs
被调用时,null
检查立即发生,然后GetFrobsForReal
在序列迭代时执行。
如果您检查 LINQ
到 Objects 的引用源,您会发现该技术始终被使用。写起来有点笨拙,但它使调试无效错误更容易。 为了调用者的方便而不是作者的方便而优化你的代码。
关于不安全代码中的 null 取消引用的说明
C#
有一个“不安全”模式,顾名思义,这是非常危险的,因为提供内存安全和类型安全的正常安全机制没有被强制执行。 除非您对内存的工作原理有透彻和深入的了解,否则不应编写不安全的代码。
在不安全模式下,您应该注意两个重要事实:
取消引用 null pointer 会产生与取消引用 null reference 相同的异常 取消引用无效的非空指针可以在某些情况下产生该异常要理解为什么会这样,首先要了解 .NET 如何产生NullReferenceException
。 (这些细节适用于在 Windows 上运行的 .NET;其他操作系统使用类似的机制。)
内存在Windows
中虚拟化;每个进程获得由操作系统跟踪的许多内存“页面”的虚拟内存空间。内存的每一页都设置了标志,这些标志决定了它可以如何使用:读取、写入、执行等等。 最低页面被标记为“如果以任何方式使用都会产生错误”。
C#
中的空指针和空引用在内部都表示为数字零,因此任何将其取消引用到相应内存存储的尝试都会导致操作系统产生错误。 .NET 运行时检测到此错误并将其转换为NullReferenceException
。
这就是解引用空指针和空引用会产生相同异常的原因。
第二点呢?取消引用 任何 位于虚拟内存最低页面的无效指针会导致相同的操作系统错误,从而导致相同的异常。
为什么这有意义?好吧,假设我们有一个包含两个 int 的结构和一个等于 null 的非托管指针。如果我们尝试取消引用结构中的第二个 int,CLR
将不会尝试访问位置 0 的存储;它将访问位置 4 的存储。但从逻辑上讲,这是一个 null 取消引用,因为我们通过 null 到达那个地址。
如果您正在使用不安全的代码并且收到NullReferenceException
,请注意违规指针不必为空。可以是最低页面的任意位置,都会产生这个异常。
【讨论】:
也许这是一个愚蠢的评论,但避免这个问题的第一个也是最好的方法不是初始化对象吗?对我来说,如果发生此错误,通常是因为我忘记初始化数组元素之类的东西。我认为将对象定义为 null 然后引用它的情况要少得多。也许给出解决与描述相邻的每个问题的方法。仍然是一个好帖子。 如果没有对象,而是方法或属性的返回值怎么办? 书/作者的例子有点奇怪....那怎么编译?智能感知是如何工作的?这是什么我不擅长电脑... @Will:我上次的编辑有帮助吗?如果不是,那么请更明确地说明您认为是什么问题。 @JohnSaunders 哦,不,抱歉,我的意思是对象初始化器版本。new Book Author = Age = 45 ;
内部初始化如何......我想不出内部初始化会工作的情况,但它可以编译并且智能感知工作......除非结构?【参考方案2】:
NullReference 异常 - Visual Basic
Visual Basic 的NullReference Exception
与C# 中的NullReference Exception
没有什么不同。毕竟,它们都报告了它们都使用的 .NET Framework 中定义的相同异常。 Visual Basic 特有的原因很少见(也许只有一个)。
此答案将使用 Visual Basic 术语、语法和上下文。使用的示例来自大量过去的 Stack Overflow 问题。这是通过使用帖子中常见的种类情况来最大化相关性。还为可能需要它的人提供了更多解释。 非常可能在此处列出了与您类似的示例。
注意:
-
这是基于概念的:没有代码可以粘贴到您的项目中。它旨在帮助您了解导致
NullReferenceException
(NRE) 的原因、如何找到它、如何修复它以及如何避免它。 NRE 可能由多种原因引起,因此这不太可能是您唯一遇到的情况。
这些示例(来自 Stack Overflow 帖子)并不总是首先显示做某事的最佳方式。
通常会使用最简单的补救措施。
基本含义
消息“Object not set to an instance of Object”表示您正在尝试使用尚未初始化的对象。这归结为以下之一:
您的代码声明了一个对象变量,但它没有初始化它(创建一个实例或'实例化'它) 您的代码假设会初始化一个对象,但事实并非如此 可能,其他代码过早地使仍在使用的对象无效寻找原因
由于问题是Nothing
的对象引用,因此答案是检查它们以找出是哪一个。然后确定它为什么没有初始化。将鼠标悬停在各种变量上,Visual Studio (VS) 将显示它们的值 - 罪魁祸首将是 Nothing
。
您还应该从相关代码中删除任何 Try/Catch 块,尤其是 Catch 块中没有任何内容的代码。这将导致您的代码在尝试使用 Nothing
的对象时崩溃。 这正是您想要的,因为它将识别问题的确切位置,并允许您识别导致问题的对象。
显示Error while...
的Catch 中的MsgBox
将无济于事。这种方法还会导致非常糟糕堆栈溢出问题,因为您无法描述实际的异常、涉及的对象,甚至是发生它的代码行。
您还可以使用Locals Window
(Debug -> Windows -> Locals)检查您的对象。
一旦您知道问题出在哪里以及问题所在,通常比发布新问题更容易解决,而且速度更快。
另见:
Breakpoints MSDN: How to: Use the Try/Catch Block to Catch Exceptions MSDN: Best Practices for Exceptions示例和补救措施
类对象/创建实例
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
这将创建一个 local 变量 reg
,它仅存在于该上下文(子)中。 reg
具有模块级别的变量 Scope
您将在其他任何地方使用它仍然是 Nothing
。
缺少
New
运算符是NullReference Exceptions
的#1 原因,在查看的堆栈溢出问题中可见。Visual Basic 尝试反复使用
New
使过程清晰:使用New
运算符创建一个 new 对象并调用Sub New
-- 构造函数 -- 您的对象可以在其中执行任何其他初始化。
需要明确的是,Dim
(或Private
)仅声明一个变量及其Type
。变量的作用域——它是否存在于整个模块/类或者是一个过程的局部——由它被声明的where决定。 Private | Friend | Public
定义访问级别,而不是 Scope。
有关详细信息,请参阅:
New Operator Scope in Visual Basic Access Levels in Visual Basic Value Types and Reference Types数组
数组也必须被实例化:
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
有关详细信息,请参阅:
Option Infer Statement Scope in Visual Basic Arrays in Visual Basic列表和集合
.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
的实例不会同时创建内部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 提供了很多机会,因为在一次。 注意:您使用的数据提供程序无关紧要——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 会警告您这可能会发生:
当声明为模块/类级别变量时,就像con
的情况一样,编译器无法知道对象是否由上游过程创建。不要忽视警告。
补救措施
Dim ds As New DataSet
示例 2
ds = New DataSet
da = New OleDBDataAdapter(sql, con)
da.Fill(ds, "Employees")
txtID.Text = ds.Tables("Employee").Rows(0).Item(1)
txtID.Name = ds.Tables("Employee").Rows(0).Item(2)
这里有一个错字:Employees
vs Employee
。没有创建名为“Employee”的DataTable
,因此NullReferenceException
结果试图访问它。另一个潜在问题是假设会有Items
,但当 SQL 包含 WHERE 子句时可能不是这样。
补救措施
由于这使用一张表,使用Tables(0)
将避免拼写错误。检查Rows.Count
也有帮助:
If ds.Tables(0).Rows.Count > 0 Then
txtID.Text = ds.Tables(0).Rows(0).Item(1)
txtID.Name = ds.Tables(0).Rows(0).Item(2)
End If
Fill
是一个返回受影响Rows
数量的函数,也可以进行测试:
If da.Fill(ds, "Employees") > 0 Then...
示例 3
Dim da As New OleDb.OleDbDataAdapter("SELECT TICKET.TICKET_NO,
TICKET.CUSTOMER_ID, ... FROM TICKET_RESERVATION AS TICKET INNER JOIN
FLIGHT_DETAILS AS FLIGHT ... WHERE [TICKET.TICKET_NO]= ...", con)
Dim ds As New DataSet
da.Fill(ds)
If ds.Tables("TICKET_RESERVATION").Rows.Count > 0 Then
DataAdapter
将提供TableNames
,如前面的示例所示,但它不会从 SQL 或数据库表中解析名称。结果,ds.Tables("TICKET_RESERVATION")
引用了一个不存在的表。
补救措施同理,按索引引用表格:
If ds.Tables(0).Rows.Count > 0 Then
另见DataTable Class。
对象路径/嵌套
If myFoo.Bar.Items IsNot Nothing Then
...
代码只是测试Items
,而myFoo
和Bar
也可能是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"
这里,myWebBrowser
或 Document
可能是 Nothing 或 formfld1
元素可能不存在。
用户界面控件
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,因为 Nothing 没有 .Text
属性
稍后引用数组元素将导致 NRE。如果您在Form_Load
中执行此操作,由于奇怪的错误,IDE可能不会在发生异常时报告异常。当您的代码尝试使用该数组时,稍后会弹出异常。这个“静默异常”是detailed in this post。就我们的目的而言,关键是当创建表单时发生灾难性事件(Sub New
或 Form Load
事件)时,可能不会报告异常,代码退出过程并仅显示表单。
由于您的 Sub New
或 Form 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没有提供警告,但补救方法是在表单级别声明容器,但在表单加载事件处理程序中初始化它们控件确实存在。这可以在Sub New
中完成,只要您的代码在InitializeComponent
调用之后:
' 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
数组代码可能还没有走出困境。容器控件中的任何控件(如GroupBox
或Panel
)都不会在Me.Controls
中找到;它们将位于该 Panel 或 GroupBox 的 Controls 集合中。当控件名称拼写错误 ("TeStBox2"
) 时,也不会返回控件。在这种情况下,Nothing
将再次存储在这些数组元素中,当您尝试引用它时将产生 NRE。
既然您知道要查找的内容,这些内容应该很容易找到:
“Button2”位于Panel
补救措施
不要使用表单的Controls
集合按名称间接引用,而是使用控件引用:
' Declaration
Private NameBoxes As TextBox()
' Initialization - simple and easy to read, hard to botch:
NameBoxes = New TextBox() TextBox1, TextBox2, ...)
' Initialize a List
NamesList = New List(Of TextBox)(TextBox1, TextBox2, TextBox3...)
' or
NamesList = New List(Of TextBox)
NamesList.AddRange(TextBox1, TextBox2, TextBox3...)
函数不返回任何内容
Private bars As New List(Of Bars) ' Declared and created
Public Function BarList() As List(Of Bars)
bars.Clear
If someCondition Then
For n As Integer = 0 to someValue
bars.Add(GetBar(n))
Next n
Else
Exit Function
End If
Return bars
End Function
在这种情况下,IDE 会警告您“并非所有路径都返回值,可能会导致 NullReferenceException
”。您可以通过将Exit Function
替换为Return Nothing
来抑制警告,但这并不能解决问题。任何尝试在someCondition = False
时使用 return 的东西都会导致 NRE:
bList = myFoo.BarList()
For Each b As Bar in bList ' EXCEPTION
...
补救措施
将函数中的Exit Function
替换为Return bList
。返回 empty 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) 应用适当的补救措施。 Try/Catch 块并不是为了向唯一有资格修复异常的人(开发人员)隐藏异常。
DBNull 不等于 Nothing
For Each row As DataGridViewRow In dgvPlanning.Rows
If Not IsDBNull(row.Cells(0).Value) Then
...
IsDBNull
函数用于测试一个值是否等于System.DBNull
:From 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
如果无法找到带有chkName
的CheckBox
(或存在于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
有AutoGenerateColumns = True
,它会创建列,但不会命名它们,所以上面的代码在通过名称引用它们时会失败。
补救措施
手动命名列,或按索引引用:
dgvBooks.Columns(0).Visible = True
示例 2 — 小心新行
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
循环,请修改行数或在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
集合只需要在应用程序第一次运行时进行初始化。另一种补救方法是在 Project -> Settings | 中为您的集合添加一个初始值。 FooBars,保存项目,然后删除假值。
要点
您可能忘记了New
运算符。
或
您认为可以完美执行以将初始化对象返回给您的代码,但事实并非如此。
不要忽略编译器警告(永远)并使用Option Strict On
(永远)。
MSDN NullReference Exception
【讨论】:
【参考方案3】:另一种情况是当您将空对象转换为value type 时。例如下面的代码:
object o = null;
DateTime d = (DateTime)o;
它会在演员表上抛出一个NullReferenceException
。在上面的示例中似乎很明显,但这可能发生在更“后期绑定”的复杂场景中,其中 null 对象已从您不拥有的某些代码返回,并且强制转换是由某些自动系统生成的。
一个例子是这个简单的带有日历控件的 ASP.NET 绑定片段:
<asp:Calendar runat="server" SelectedDate="<%#Bind("Something")%>" />
这里,SelectedDate
实际上是 DateTime
类型的属性 - Calendar
Web Control 类型的属性,并且绑定可以完美地返回 null。隐式 ASP.NET 生成器将创建一段与上面的强制转换代码等效的代码。这将引发一个很难发现的NullReferenceException
,因为它位于 ASP.NET 生成的代码中,可以很好地编译...
【讨论】:
很棒的收获。单线避免方法:DateTime x = (DateTime) o as DateTime? ?? defaultValue;
【参考方案4】:
这意味着您的代码使用了一个设置为 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】:这意味着有问题的变量没有指向任何东西。我可以这样生成:
SqlConnection connection = null;
connection.Open();
这将引发错误,因为虽然我已经声明了变量“connection
”,但它并没有指向任何东西。当我尝试调用成员“Open
”时,没有可解决的参考,它会抛出错误。
为避免此错误:
-
在尝试对对象执行任何操作之前,请始终初始化对象。
如果您不确定对象是否为空,请使用
object == null
进行检查。
JetBrains 的ReSharper 工具将识别代码中可能出现空引用错误的每个位置,从而允许您进行空检查。这个错误是错误的第一来源,恕我直言。
【讨论】:
JetBrains 的 Resharper 工具将识别代码中可能出现空引用错误的每个地方。 这是不正确的。我有一个没有该检测的解决方案,但代码偶尔会导致异常。我怀疑在涉及多线程时偶尔无法检测到 - 至少被他们检测到,但我无法进一步评论,因为我还没有确定我的错误的位置。 但是当使用 HttpContext.Current.Responce.Clear() 出现 NullReferenceException 时如何解决。上述任何解决方案都无法解决它。因为在创建 HttpContext 的对象对象时,会出现错误“重载解析失败,因为没有可访问的 'New' 接受此参数数。【参考方案6】:请注意,无论哪种情况,.NET 中的原因总是相同的:
您正在尝试使用值为
Nothing
/null
的引用变量。当引用变量的值为Nothing
/null
时,这意味着它实际上并未持有对堆上存在的任何对象实例的引用。您要么从未为变量分配任何内容,从未创建分配给变量的值的实例,要么您手动将变量设置为等于
Nothing
/null
,或者您调用了一个将变量设置为的函数Nothing
/null
给你。
【讨论】:
【参考方案7】:抛出此异常的一个示例是:当您尝试检查某事时,该异常为 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 and Null Parameter 中。
【讨论】:
【参考方案8】:Update C#8.0, 2019: Nullable reference types
C#8.0 引入了可为空的引用类型和不可为空的引用类型。所以只有可以为空的引用类型必须被检查以避免NullReferenceException。
如果您尚未初始化引用类型,并且想要设置或读取其属性之一,它将抛出 NullReferenceException。
例子:
Person p = null;
p.Name = "Harry"; // NullReferenceException occurs here.
您可以通过检查变量是否为空来避免这种情况:
Person p = null;
if (p!=null)
p.Name = "Harry"; // Not going to run to this point
要全面了解引发 NullReferenceException 的原因,了解value types 和[引用类型][3] 之间的区别很重要。
因此,如果您处理的是值类型,NullReferenceExceptions 可能不会发生。虽然您在处理引用类型时需要保持警惕!
只有引用类型,顾名思义,可以保存引用或直接指向任何内容(或“null”)。而值类型总是包含一个值。
参考类型(这些必须勾选):
动态 对象 字符串值类型(您可以直接忽略这些):
数字类型 整数类型 浮点类型 十进制 布尔 用户定义的结构【讨论】:
-1:因为问题是“什么是 NullReferenceException”,所以值类型不相关。 @John Saunders:我不同意。作为软件开发人员,能够区分值类型和引用类型非常重要。否则人们最终会检查整数是否为空。 没错,只是不在这个问题的上下文中。 感谢您的提示。我对其进行了一些改进,并在顶部添加了一个示例。我仍然认为提及引用和值类型很有用。 我认为您没有添加其他答案中没有的任何内容,因为该问题预先假定了引用类型。【参考方案9】:NullReferenceExceptions
可能发生的另一种情况是as
operator 的(不正确)使用:
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)
// ...
【讨论】:
当unboxing 是一个变量时,这可能会发生很多。在我更改 UI 元素的类型但忘记更新代码隐藏后,我发现它经常发生在事件处理程序中。【参考方案10】:您正在使用包含空值引用的对象。所以它给出了一个空异常。在示例中,字符串值为 null,并且在检查其长度时发生了异常。
例子:
string value = null;
if (value.Length == 0) // <-- Causes exception
Console.WriteLine(value); // <-- Never reached
异常错误是:
未处理的异常:
System.NullReferenceException:对象引用未设置为实例 的一个对象。在 Program.Main()
【讨论】:
多么深刻!我从未将“null”常量视为参考值。所以这就是 C# 抽象“NullPointer”的方式吧? B/c 我记得在 C++ 中,NPE 可能是由取消引用未初始化的指针(即 c# 中的 ref 类型)引起的,其默认值恰好是未分配给该进程的地址(很多情况下,这将是 0,特别是在更高版本的 C++ 中,它属于操作系统 - f 与它一起进行自动初始化并死掉beeotch(或者只是捕捉操作系统攻击您的进程的信号))。【参考方案11】:虽然 what 会导致 NullReferenceExceptions 并且 avoid/fix 这种异常的方法已在其他答案中得到解决,但许多程序员尚未了解的是如何在开发过程中独立调试此类异常。
在 Visual Studio 中,由于Visual Studio Debugger,这通常很容易。
首先,确保将捕获正确的错误 - 请参阅 How do I allow breaking on 'System.NullReferenceException' in VS2010? 注意1
然后是Start with Debugging (F5) 或Attach [the VS Debugger] to Running Process。有时使用Debugger.Break
可能很有用,它会提示启动调试器。
现在,当 NullReferenceException 被抛出(或未处理)时,调试器将在发生异常的行停止(还记得上面设置的规则吗?)。有时错误很容易被发现。
例如,
在以下行中,可以 导致异常的唯一代码是myString
的计算结果为 null。这可以通过查看Watch Window 或在Immediate Window 中运行表达式来验证。
var x = myString.Trim();
在更高级的情况下,例如以下情况,您需要使用上述技术之一(Watch 或 Immediate Windows)来检查表达式以确定 str1
是否为 null 或 str2
是否为 null。
var x = str1.Trim() + str2.Trim();
一旦在哪里找到异常抛出的位置,通常很容易向后推理以找出 [不正确] 引入空值的位置 --
花点时间了解异常的原因。检查空表达式。检查可能导致此类空表达式的先前表达式。添加breakpoints 并酌情逐步执行该程序。 使用调试器。
1 如果 Break on Throws 过于激进,并且调试器在 .NET 或 3rd-party 库中的 NPE 上停止,Break on User-Unhandled 可用于限制捕获的异常。此外,VS2012 引入了Just My Code,我建议也启用它。
如果您在启用“仅我的代码”的情况下进行调试,则行为会略有不同。启用“仅我的代码”后,调试器会忽略在“我的代码”之外引发且不通过“我的代码”的第一次机会公共语言运行时 (CLR) 异常
【讨论】:
【参考方案12】:Simon Mourier gave this example:
object o = null;
DateTime d = (DateTime)o; // NullReferenceException
其中 拆箱 转换(转换)来自 object
(或来自System.ValueType
或System.Enum
类之一,或从接口类型)到值类型(Nullable<>
除外)本身给出了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<>
实例时使用的特殊规则。
【讨论】:
【参考方案13】:添加实体框架中使用的实体类名与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;
当实体和代码隐藏类在同一个命名空间中时会发生错误。 要解决此问题,请重命名 Contact.aspx 的实体类或代码隐藏类。
原因 我仍然不确定原因。但是,只要任何实体类扩展 System.Web.UI.Page 就会发生此错误。
如需讨论,请查看NullReferenceException in DbContext.saveChanges()
【讨论】:
【参考方案14】:另一个可能收到此异常的一般情况涉及在单元测试期间模拟类。无论使用何种模拟框架,您都必须确保正确模拟类层次结构的所有适当级别。特别是,HttpContext
的所有被测试代码引用的属性都必须被模拟。
有关详细示例,请参阅“NullReferenceException thrown when testing custom AuthorizationAttribute”。
【讨论】:
【参考方案15】:我对回答这个问题有不同的看法。这种回答“我还能做些什么来避免它?”
当跨不同层工作时,例如在 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();
【讨论】:
-1:这只处理一个场景——未初始化的依赖关系。这是 NullReferenceException 的少数情况。大多数情况是对对象如何工作的简单误解。接下来最常见的是开发人员假设对象会自动初始化的其他情况。 一般不使用依赖注入以避免NullReferenceException。我不相信您在这里找到了一般情况。无论如何,如果您将答案编辑为更多***.com/a/15232518/76337 的样式,那么我将删除反对票。【参考方案16】:关于“我该怎么办”,可以有很多答案。
在开发过程中 防止此类错误情况的一种更“正式”的方法是在您的代码中应用design by contract。这意味着您需要在开发时在系统上设置类不变量,甚至函数/方法前置条件和后置条件。
简而言之,类不变量确保您的类中有一些在正常使用中不会被违反的约束(因此,类将进入不一致的状态)。 前置条件意味着作为函数/方法输入的数据必须遵循一些约束集并且从不违反它们,而后置条件意味着函数/方法输出必须再次遵循设置的约束,而不会违反它们。 在执行无错误的程序期间,应绝不违反合同条件,因此在调试模式下实际检查合同设计,同时在版本中禁用,以最大限度地提高开发系统性能。
这样,您可以避免因违反约束集而导致的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);
//...
因此,Code Contracts 项目存在用于 .NET 应用程序。
或者,可以使用 assertions 应用按合同设计。
更新:值得一提的是,该术语是由 Bertrand Meyer in connection with his design of the Eiffel programming language 创造的。
【讨论】:
我想添加这个,因为没有人提到这个,并且就它作为一种方法存在而言,我的目的是丰富这个话题。 我认为这是对主题的一个值得补充的内容,因为这是一个备受关注的主题。我以前听说过代码合同,这很好地提醒我考虑使用它们。【参考方案17】:当我们尝试访问空对象的属性或字符串值变为空并且我们尝试访问字符串方法时,会抛出NullReferenceException
。
例如:
当一个空字符串的字符串方法被访问时:
string str = string.Empty;
str.ToLower(); // throw null reference exception
当一个空对象的属性被访问时:
Public Class Person
public string Name get; set;
Person objPerson;
objPerson.Name /// throw Null refernce Exception
【讨论】:
这是不正确的。String.Empty.ToLower()
不会抛出空引用异常。它代表一个实际的字符串,尽管是一个空字符串(即""
)。因为这有一个对象可以调用ToLower()
,所以在那里抛出一个空引用异常是没有意义的。【参考方案18】:
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
),所以我知道错误是我的发送模型。【参考方案19】:
你能做些什么?
这里有很多很好的答案来解释什么是空引用以及如何调试它。但是很少有关于如何预防这个问题或至少让它更容易被发现的问题。
检查参数
例如,方法可以检查不同的参数以查看它们是否为空并抛出ArgumentNullException
,显然是为此目的创建的异常。
ArgumentNullException
的构造函数甚至将参数名称和消息作为参数,这样您就可以准确地告诉开发人员问题所在。
public void DoSomething(MyObject obj)
if(obj == null)
throw new ArgumentNullException("obj", "Need a reference to obj.");
使用工具
还有几个库可以提供帮助。例如,“Resharper”可以在您编写代码时为您提供警告,特别是如果您使用它们的属性:NotNullAttribute
在“Microsoft 代码合同”中,您可以使用 Contract.Requires(obj != null)
之类的语法,为您提供运行时和编译检查:Introducing Code Contracts。
还有“PostSharp”,它允许你只使用这样的属性:
public void DoSometing([NotNull] obj)
通过这样做并使 PostSharp 成为构建过程的一部分 obj
将在运行时检查是否为空。见:PostSharp null check
纯代码解决方案
或者您可以随时使用普通的旧代码编写自己的方法。例如,这是一个可用于捕获空引用的结构。它与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
null. Therefore you only have to check whether
addressis
null`。
这是一个很棒的功能,但它提供的信息更少。这 4 个中的哪一个为 null 并不明显。
像 Nullable 一样内置?
C# 有一个很好的 Nullable<T>
简写,你可以通过在 int?
这样的类型后面加上一个问号来使某些东西可以为空。
如果 C# 有类似上面的 NotNull<T>
结构并且有类似的速记,也许是感叹号 (!) 这样你就可以写出类似的东西:public void WriteName(Person! person)
。
【讨论】:
永远不要抛出 NullReferenceException @JohnSaunders 我敢问为什么? (说真的,为什么?) NullReferenceException 是由 CLR 抛出的。这意味着发生了对空值的引用。这并不意味着会发生对 null 的引用,除非您先巧妙地检查。 我明白你的观点,这会让人感到困惑。我已将其更新为此示例的常规异常和 GitHub 中的自定义异常。 这样一个基本问题的好答案。当您的代码失败时,情况还不错。当它来自您所依赖的某个商业第三方库的深处时,这太可怕了,并且客户支持一直坚持认为它必须是您的代码导致了问题。而且你不完全确定它不是,整个项目都停止了。我实际上认为这可能会为我的墓碑制作一个合适的墓志铭:“对象引用未设置为对象的实例。”【参考方案20】:您可以使用 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 时 name 将为 null。
否则,变量名将被赋予 p.Spouse.FirstName 的值。
更多详情:Null-conditional Operators
【讨论】:
【参考方案21】:有趣的是,此页面上的答案都没有提到两种极端情况:
边缘案例 #1:同时访问字典
.NET 中的通用字典不是线程安全的,当您尝试从两个并发线程访问密钥时,它们有时可能会抛出 NullReference
甚至(更频繁地)KeyNotFoundException
.在这种情况下,这个例外非常具有误导性。
边缘案例 #2:不安全的代码
如果unsafe
代码抛出NullReferenceException
,您可能会查看指针变量,并检查它们是否有IntPtr.Zero
或其他内容。这是同一件事(“空指针异常”),但在不安全的代码中,变量通常被强制转换为值类型/数组等,并且你把头撞在墙上,想知道值类型如何抛出这个例外。
(顺便说一句,除非你需要,否则不使用不安全代码的另一个原因。)
边缘案例 #3:Visual Studio 多显示器设置,辅助显示器的 DPI 设置与主显示器不同
此边缘案例是特定于软件的,适用于 Visual Studio 2019 IDE(可能还有早期版本)。
重现该问题的方法:将任何组件从工具箱拖到具有与主监视器不同 DPI 设置的非主监视器上的 Windows 窗体,然后您会收到一个弹出窗口,其中显示“对象引用未设置为一个对象的实例。”根据this thread 的说法,这个问题已经知道很长一段时间了,在撰写本文时仍未解决。
【讨论】:
您的字典示例不是极端情况。如果对象不是线程安全的,那么从多个线程中使用它会产生随机结果。您的不安全代码示例与null
有何不同?【参考方案22】:
错误行“对象引用未设置为对象的实例”。表示您尚未将实例对象分配给对象引用,并且仍在访问该对象的属性/方法。
例如:假设您有一个名为 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 an error
上面的行抛出一个错误,因为声明了类 myClass 的引用,但没有实例化,或者没有将对象的实例分配给该类的引用。
要解决此问题,您必须实例化(将对象分配给该类的引用)。
public class Demo
public void testMethod()
myClass ref = null;
ref = new myClass();
ref.prop1 = 1;
【讨论】:
【参考方案23】:当您尝试使用的类的对象未实例化时,会发生 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。
【讨论】:
【参考方案24】:嗯,简单来说:
您正在尝试访问未创建或当前不在内存中的对象。
那么如何解决这个问题:
调试并让调试器中断...它会直接将您带到损坏的变量...现在您的任务是简单地修复此问题..使用 new 关键字在适当的地方。
如果它是由某些 database 命令引起的,因为对象不存在,那么您需要做的就是做一个空检查并处理它:
if (i == null)
// Handle this
最难的..如果 GC 已经收集了对象...如果您尝试使用字符串查找对象,通常会发生这种情况...也就是说,找到它是对象的名称,那么可能会发生 GC 可能已经清理了它...这很难找到并且会成为一个很大的问题...解决这个问题的更好方法是在开发过程中进行必要的空检查过程。这将为您节省大量时间。
按名称查找是指某些框架允许您使用字符串查找对象,代码可能如下所示:FindObject("ObjectName");
【讨论】:
如果你有一个对象的引用,那么 GC 永远不会清理它 如果您使用 FindObject("Name of Object") 之类的东西,GC 不会事先知道您将要引用该对象..这就是试图解释的内容..这些发生在运行时 有一些框架在 C# 中提供此功能,例如 Unity。这个问题与 BCl 无关。在批评之前先在互联网上搜索,有很多类似的功能,对于你的好消息,我什至每天都在使用它。现在请告诉我答案怎么没有意义。 docs.unity3d.com/ScriptReference/… 检查链接并更正自己,先生。专家:p 我在您的链接中看到的示例将 GameObject.Find 的结果分配给成员字段。这是一个引用,在收集到包含对象之前,GC 不会收集它。【参考方案25】:实际上修复 NullReferenceExeption 的最简单方法有两种。
如果您有一个 GameObject,例如附加了一个脚本和一个名为 rb(刚体)的变量,则当您开始游戏时,此变量将以 null 开头。 这就是您得到 NullReferenceExeption 的原因,因为计算机没有将数据存储在该变量中。
我将使用 RigidBody 变量作为示例。 实际上,我们可以通过以下几种方式轻松添加数据:
-
使用 AddComponent > Physics > Rigidbody 将 RigidBody 添加到您的对象
然后进入你的脚本并输入
rb = GetComponent<Rigidbody>();
这行代码在您的 Start()
或 Awake()
函数下效果最佳。
您可以通过编程方式添加组件并同时使用一行代码分配变量:rb = AddComponent<RigidBody>();
进一步说明:如果您希望Unity 向您的对象添加一个组件,而您可能忘记添加一个,您可以在您的类声明上方输入[RequireComponent(typeof(RigidBody))]
(您的所有使用s)。
享受制作游戏的乐趣!
【讨论】:
【参考方案26】:如果在保存或编译构建期间收到此消息,只需关闭所有文件,然后打开任何文件进行编译和保存。
对我来说,原因是我重命名了文件并且旧文件仍然打开。
【讨论】:
【参考方案27】:这基本上是一个空引用异常。正如Microsoft 所说-
当您尝试访问一个 NullReferenceException 异常 值为 null 的类型的成员。
这是什么意思?
这意味着如果任何成员不持有任何价值并且我们让该成员执行某些任务,那么系统无疑会抛出一条消息并说-
“嘿等等,那个成员没有价值,所以它不能执行你交给它的任务。”
异常本身表示正在引用某些内容,但未设置其值。所以这表示它仅在使用引用类型时发生,因为值类型不可为空。
如果我们使用值类型成员,则不会发生 NullReferenceException。
class Program
static void Main(string[] args)
string str = null;
Console.WriteLine(str.Length);
Console.ReadLine();
上面的代码显示了一个简单的字符串,它被分配了一个 null 值。
现在,当我尝试打印字符串 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 的出现是由于我们没有检查对象的值的错误。我们经常在代码开发中不检查对象值。
当我们忘记实例化我们的对象时也会出现这种情况。使用可以返回或设置空值的方法、属性、集合等也可能是导致此异常的原因。
如何避免?
有多种方法和方法可以避免这个著名的异常:
显式检查:我们应该坚持检查对象、属性、方法、数组和集合是否为空的传统。这可以使用 if-else if-else 等条件语句来简单地实现。
异常处理:管理此异常的重要方法之一。使用简单的 try-catch-finally 块,我们可以控制这个异常并维护它的日志。这在您的应用程序处于生产阶段时非常有用。
空运算符:空合并运算符和空条件运算符也可以在为对象、变量、属性和字段设置值时方便地使用。
调试器:对于开发人员来说,我们有调试的大武器。如果我们在开发过程中遇到 NullReferenceException,我们可以使用调试器找到异常的来源。
内置方法:GetValueOrDefault()、IsNullOrWhiteSpace() 和 IsNullorEmpty() 等系统方法检查是否存在空值,如果存在空值,则分配默认值。
这里已经有很多很好的答案。您也可以在我的blog 上查看更详细的示例说明。
希望这也有帮助!
【讨论】:
以上是关于什么是 NullReferenceException,我该如何解决?的主要内容,如果未能解决你的问题,请参考以下文章