我有一些代码,当它执行时,它会抛出一个 NullReferenceException
,说:
你调用的对象是空的。
这是什么意思,我该怎么做才能解决这个错误?
我有一些代码,当它执行时,它会抛出一个 NullReferenceException
,说:
你调用的对象是空的。
这是什么意思,我该怎么做才能解决这个错误?
你正在尝试使用的东西 null
(要么 Nothing
在VB.NET中)。这意味着您要么将其设置为 null
,或者你从来没有把它设置成任何东西。
像其他任何东西, null
得到传递。如果是 null
在 方法“A”,可能是方法“B”通过了 null
至 方法“A”。
null
可以有不同的含义:
NullReferenceException
。null
故意表明没有有意义的价值。 请注意,C#具有变量的可空数据类型的概念(如数据库表可以有可空字段) - 您可以分配 null
例如,向他们表明没有存储在其中的值 int? a = null;
其中问号表示允许在变量中存储null a
。你可以用它来检查 if (a.HasValue) {...}
或者 if (a==null) {...}
。可空的变量,如 a
这个例子,允许通过访问值 a.Value
明确地,或者正常通过 a
。 a.Value
抛出一个 InvalidOperationException
代替 NullReferenceException
如果 a
是 null
- 您应该事先进行检查,即如果您有另一个可以为空的变量 int b;
然后你应该做像 if (a.HasValue) { b = a.Value; }
或更短 if (a != null) { b = a; }
。本文的其余部分将详细介绍并显示许多程序员经常犯的错误,这些错误可导致错误 NullReferenceException
。
运行时抛出一个 NullReferenceException
总是 意思是同一件事:你试图使用一个引用,并且引用没有被初始化(或者它是 一旦 初始化,但是 不再 初始化)。
这意味着参考是 null
,并且您无法通过a访问成员(例如方法) null
参考。最简单的情况:
string foo = null;
foo.ToUpper();
这将抛出一个 NullReferenceException
在第二行,因为你无法调用实例方法 ToUpper()
在...上 string
引用指向 null
。
你如何找到一个来源 NullReferenceException
?除了查看异常本身,它将完全抛出它发生的位置,Visual Studio中的调试的一般规则适用:放置战略断点和 检查你的变量,将鼠标悬停在其名称上,打开(快速)监视窗口或使用各种调试面板(如本地和汽车)。
如果要查找引用的位置或未设置,请右键单击其名称并选择“查找所有引用”。然后,您可以在每个找到的位置放置断点,并在附加调试器的情况下运行程序。每次调试器在这样的断点上中断时,您需要确定是否期望引用为非null,检查变量并验证它是否指向实例。
通过这种方式跟踪程序流,您可以找到实例不应为null的位置,以及未正确设置的原因。
可以抛出异常的一些常见场景:
ref1.ref2.ref3.member
如果ref1或ref2或ref3为null,那么你将得到一个 NullReferenceException
。如果你想解决这个问题,那么通过将表达式重写为更简单的等价物来找出哪一个是null:
var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member
具体来说,在 HttpContext.Current.User.Identity.Name
, HttpContext.Current
可能是null,或者是 User
属性可以为null,或者 Identity
属性可以为null。
public class Person {
public int Age { get; set; }
}
public class Book {
public Person Author { get; set; }
}
public class Example {
public void Foo() {
Book b1 = new Book();
int authorAge = b1.Author.Age; // You never initialized the Author property.
// there is no Person to get an Age from.
}
}
如果要避免子(Person)null引用,可以在父(Book)对象的构造函数中初始化它。
这同样适用于嵌套对象初始值设定项:
Book b1 = new Book { Author = { Age = 45 } };
这转化为
Book b1 = new Book();
b1.Author.Age = 45;
虽然 new
使用关键字,它只创建一个新的实例 Book
,但不是新的实例 Person
, 所以 Author
该物业仍在 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;
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!";
}
}
// if the "FirstName" session value has not yet been set,
// then this line will throw a NullReferenceException
string firstName = Session["FirstName"].ToString();
如果引用属性时发生异常 @Model
在ASP.NET MVC视图中,您需要了解它 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控件是在调用期间创建的 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;
这不会抛出InvalidCastException但会返回一个 null
当转换失败时(当someObject本身为null时)。所以要注意这一点。
普通版本 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,则可以检查它是否为空 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()
可以为null的值类型提供默认值 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]。a时提供默认值的简写 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();
这将导致title变量 null
,和呼吁 ToUpper
如果没有 person.Title
是 null
。
当然,你 仍然 必须检查 title
for null或使用null条件运算符和null coalescing运算符(??
)提供默认值:
// 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日 元件。
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参数异常移动到 迭代,不是为了 呼叫, 那就是 调试非常混乱。
正确的解决方法是:
// DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
// No yields in a public method that throws!
if (f == null)
throw new ArgumentNullException("f", "factory must not be null");
return GetFrobsForReal(f, count);
}
private IEnumerable<Frob> GetFrobsForReal(FrobFactory f, int count)
{
// Yields in a private method
Debug.Assert(f != null);
for (int i = 0; i < count; ++i)
yield return f.MakeFrob();
}
也就是说,创建一个具有迭代器块逻辑的私有辅助方法,以及一个执行空检查并返回迭代器的公共表面方法。现在,当 GetFrobs
被调用,空检查立即发生,然后 GetFrobsForReal
迭代序列时执行。
如果检查LINQ to Objects的参考源,您将看到始终使用此技术。写入稍微笨拙,但它使调试无效性错误变得更加容易。 优化代码以方便调用者,而不是作者的便利。
C#具有“不安全”模式,顾名思义,这种模式极其危险,因为不强制执行提供存储器安全性和类型安全性的正常安全机制。 除非您对内存的工作原理有深入的了解,否则不应编写不安全的代码。
在不安全模式下,您应该了解两个重要事实:
要理解为什么这样做,首先要了解.NET如何产生空解除引用异常。 (这些细节适用于在Windows上运行的.NET;其他操作系统使用类似的机制。)
内存在Windows中虚拟化;每个进程获得由操作系统跟踪的许多“页面”内存的虚拟内存空间。每页内存都设置了标志,用于确定如何使用它:读取,写入,执行等。该 最低 页面被标记为“如果以任何方式使用则产生错误”。
C#中的空指针和空引用都在内部表示为数字零,因此任何将其取消引用到其相应的内存存储中的尝试都会导致操作系统产生错误。然后,.NET运行时检测到此错误并将其转换为空解除引用异常。
这就是为什么解除引用空指针和空引用会产生相同的异常。
第二点怎么样?取消引用 任何 落在虚拟内存最低页面中的无效指针会导致相同的操作系统错误,从而导致相同的异常。
为什么这有意义?好吧,假设我们有一个包含两个int的结构,一个等于null的非托管指针。如果我们尝试取消引用结构中的第二个int,CLR将不会尝试访问零位置的存储;它将访问位置四的存储。但从逻辑上讲,这是一个空取消引用,因为我们到达那个地址 通过 null。
如果您正在使用不安全的代码并且您获得了空解除引用异常,请注意违规指针不必为空。它可以是最低页面中的任何位置,并且将生成此异常。
该 NullReference Exception
对于 Visual Basic 与中的没什么不同 C#。毕竟,他们都报告了他们都使用的.NET Framework中定义的相同异常。 Visual Basic特有的原因很少见(可能只有一个)。
这个答案将使用Visual Basic术语,语法和上下文。使用的示例来自大量过去的Stack Overflow问题。这是为了通过使用最大化相关性 种 在帖子中常见的情况。还为那些可能需要它的人提供了更多的解释。一个类似于你的例子是 非常 可能在这里列出。
注意:
NullReferenceException
(NRE),如何找到它,如何修复它,以及如何避免它。 NRE可以通过多种方式引起,因此这不太可能是您唯一的遭遇。消息 “对象未设置为Object的实例” 意味着您正在尝试使用尚未初始化的对象。这归结为以下之一:
由于问题是一个对象引用 Nothing
,答案是检查它们以找出哪一个。然后确定它未初始化的原因。将鼠标悬停在各种变量上,Visual Studio(VS)将显示其值 - 罪魁祸首 Nothing
。
您还应该从相关代码中删除任何Try / Catch块,尤其是Catch块中没有任何内容的块。这会导致代码在尝试使用对象时崩溃 Nothing
。 这就是你想要的 因为它会识别确切的 位置 问题,并允许您识别导致它的对象。
一个 MsgBox
在显示的Catch中 Error while...
会有点帮助。这种方法也导致了 很坏 Stack Overflow问题,因为你无法描述实际的异常,涉及的对象甚至代码行所在的位置。
你也可以使用 Locals Window
(调试 - > Windows - >本地)检查你的对象。
一旦你知道问题是什么以及在哪里,它通常很容易修复,并且比发布新问题更快。
也可以看看:
Dim reg As CashRegister
...
TextBox1.Text = reg.Amount ' NRE
问题是 Dim
不会创建CashRegister 目的;它只声明一个名为的变量 reg
那种类型。 声明 一个对象变量并创建一个 例 是两件不同的事情。
补救
该 New
在声明实例时,通常可以使用operator来创建实例:
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
,仅存在于该上下文(sub)中。该 reg
模块级别的变量 Scope
你会在其他地方使用它仍然存在 Nothing
。
错过了
New
运营商是第一个原因NullReference Exceptions
在Stack Overflow中看到的问题已经过审核。Visual Basic尝试使用重复清除进程
New
: 使用New
运营商创建一个 新 对象和电话Sub New
- 构造函数 - 您的对象可以在其中执行任何其他初始化。
要清楚, Dim
(要么 Private
) 只要 声明 变量及其变量 Type
。该 范围 变量 - 是否存在于整个模块/类中或者是过程的本地变量 - 取决于 哪里 它被宣布。 Private | Friend | Public
不定义访问级别 范围。
有关更多信息,请参阅:
还必须实例化数组:
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
有关更多信息,请参阅:
还必须实例化或创建.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)
类。
使用数据库为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
声明了数据集对象,但从未创建过实例。该 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
。没有 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类。
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
可能是什么都没有 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...
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 不得 发生异常时报告异常。弹出异常 后来 当您的代码尝试使用该数组时。这个“沉默的例外”是 在这篇文章中详述。对于我们的目的,关键是当创建表单时发生灾难性事件(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
阵列代码可能还没有走出困境。容器控件中的任何控件(如a 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会警告你的情况'并非所有路径都返回值和a NullReferenceException
可能的结果”。您可以通过替换来取消警告 Exit Function
同 Return Nothing
,但这并没有解决问题。任何试图使用返回的东西 someCondition = False
将导致NRE:
bList = myFoo.BarList()
For Each b As Bar in bList ' EXCEPTION
...
补救
更换 Exit Function
在函数中 Return bList
。回来了 空 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感到困惑,为什么他要获得NRE Finally
块。在其他情况下,空 Catch
可能会导致更多下游的其他事情变得混乱,并导致您花时间在错误的地方查找错误的问题。 (上述“无声例外”提供相同的娱乐价值。)
补救
不要使用空的Try / Catch块 - 让代码崩溃,以便a)确定原因b)识别位置和c)应用适当的补救措施。 Try / Catch块不是为了隐藏来自唯一有资格修复它们的人的例外 - 开发人员。
For Each row As DataGridViewRow In dgvPlanning.Rows
If Not IsDBNull(row.Cells(0).Value) Then
...
该 IsDBNull
函数用于测试a 值 等于 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
如果一个 CheckBox
同 chkName
无法找到(或存在于 GroupBox
), 然后 chk
将是Nothing并且试图引用任何属性将导致异常。
补救
If (chk IsNot Nothing) AndAlso (chk.Checked) Then ...
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
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 For
什么时候 IsNewRow
是真的。
在某些情况下,尝试使用来自的项目 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
(总是)。
另一种情况是将一个空对象转换为一个 值类型。例如,下面的代码:
object o = null;
DateTime d = (DateTime)o;
它会抛出一个 NullReferenceException
在演员。在上面的示例中似乎很明显,但这可能发生在更多“后期绑定”错综复杂的场景中,其中null对象已从您不拥有的某些代码返回,并且转换例如由某个自动系统生成。
其中一个例子是这个带有Calendar控件的简单ASP.NET绑定片段:
<asp:Calendar runat="server" SelectedDate="<%#Bind("Something")%>" />
这里, SelectedDate
实际上是一个属性 - DateTime
类型 - 的 Calendar
Web控件类型,绑定可以完美地返回null。隐式ASP.NET生成器将创建一段代码,它将等同于上面的强制转换代码。这将提出一个 NullReferenceException
这很难被发现,因为它位于ASP.NET生成的代码中,编译很好......
这意味着有问题的变量无效。我可以这样生成:
SqlConnection connection = null;
connection.Open();
这将抛出错误,因为我已经声明变量“connection
“,它没有指向任何东西。当我试图给会员打电话时”Open
“,没有任何参考可以解决它,它会抛出错误。
要避免此错误:
object == null
。JetBrains的Resharper工具将识别代码中可能存在空引用错误的每个位置,允许您进行空检查。这个错误是错误的头号来源,恕我直言。
这意味着您的代码使用了一个设置为null的对象引用变量(即它没有引用实际的对象实例)。
为了防止错误,可以在使用之前测试null为null的对象为null。
if (myvar != null)
{
// Go ahead and use myvar
myvar.property = ...
}
else
{
// Whoops! myvar is null and cannot be used without first
// assigning it to an instance reference
// Attempting to use myvar here will result in NullReferenceException
}
请注意,无论情况如何,.NET中的原因始终相同:
您正在尝试使用值为的引用变量
Nothing
/null
。当值是Nothing
/null
对于引用变量,这意味着它实际上并不持有对堆上存在的任何对象的实例的引用。您从未向变量分配内容,从未创建分配给变量的值的实例,或者您将变量设置为等于
Nothing
/null
手动,或者您调用了一个将变量设置为的函数Nothing
/null
为你。
抛出此异常的一个示例是:当您尝试检查某些内容时,该值为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参数。