什么是NullReferenceException,以及如何解决它?
我有一些代码,当它执行时,它会抛出一个NullReferenceException
,并说:
你调用的对象是空的。
这是什么意思,我能做些什么来解决这个错误?
原因是什么?
底线
您试图使用null
(或VB.NET中的Nothing
)。 这意味着你要么将它设置为null
,要么你根本没有设置它。
像其他任何东西一样, null
传递。 如果它在方法“A”中为null
,则可能是该方法“B”将null
传递给方法“A”。
本文的其余部分会更详细地介绍并显示许多程序员经常犯的错误,这可能会导致NullReferenceException
。
进一步来说
NullReferenceException
的运行时总是意味着同样的事情:您试图使用引用,并且引用未初始化(或者它曾经初始化,但不再初始化)。
这意味着引用为null
,并且您无法通过null
引用访问成员(例如方法)。 最简单的情况:
string foo = null;
foo.ToUpper();
这将在第二行中抛出NullReferenceException
,因为您无法在指向null
的string
引用上调用实例方法ToUpper()
。
调试
你如何找到NullReferenceException
的来源? 除了查看异常本身,这些异常本身将会发生在它发生的位置,Visual Studio中调试的一般规则适用于:放置战略断点并检查变量,方法是将鼠标悬停在其名称上,打开(快速)观察窗口或使用各种调试面板,如本地和汽车。
如果您想查找引用的位置或未设置,请右键单击其名称并选择“查找所有引用”。 然后,您可以在每个找到的位置放置一个断点,并使用附加的调试器运行您的程序。 每当调试器断开这样的断点时,您需要确定您是否期望引用是非空的,检查变量并验证它是否指向实例。
通过这种方式遵循程序流程,您可以找到实例不应该为null的位置,以及为什么它没有正确设置。
例子
可引发异常的一些常见情况:
通用
ref1.ref2.ref3.member
如果ref1或ref2或ref3为null,那么您将得到一个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.
}
}
如果你想避免child(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
的属性时发生异常,则需要了解在return
视图时, 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控件创建顺序和事件
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
。
用as
投射
var myThing = someObject as Thing;
这并不抛出一个InvalidCastException但返回null
时,转换失败(当someObject本身就是空)。 所以要注意这一点。
LINQ FirstOrDefault()和SingleOrDefault()
没有任何内容时,Plain First()
和Single()
异常。 在这种情况下,“OrDefault”版本返回null。 所以要注意这一点。
的foreach
当您尝试迭代空集合时, 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;
}
如果值不应为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.
}
虽然此检查不会在您的发行版本中结束,但在发布模式下运行时book == null
时,会导致它再次抛出NullReferenceException
。
对于可为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;
}
使用null条件运算符: ?.
或?[x]
用于数组(可用于C#6和VB.NET 14):
这有时也被称为安全导航或埃尔维斯(在它的形状之后)运营商。 如果运算符左侧的表达式为空,则不会评估右侧,而是返回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
,并且如果person.Title
为null
则不会调用ToUpper
。
当然,您仍然需要检查null的title
,或者将空条件运算符与空合并运算符( ??
)一起使用以提供默认值:
// 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个元素。
NullReference异常 - Visual Basic
Visual Basic的NullReference Exception
与C#中的NullReference Exception
没有区别。 毕竟,他们都报告了它们都使用的.NET Framework中定义的相同异常。 Visual Basic特有的原因很少见(可能只有一种)。
这个答案将使用Visual Basic术语,语法和上下文。 所使用的示例来自大量过去的Stack Overflow问题。 这是通过使用帖子中常见的情况来最大化相关性。 还为可能需要它的人提供了更多解释。 类似于你的例子很可能在这里列出。
注意:
NullReferenceException
(NRE)的原因,如何找到它,如何解决该问题以及如何避免它。 NRE可能会有很多种方式,所以这不太可能是你唯一的遭遇。 基本含义
“对象未设置为对象实例”消息表示您正在尝试使用尚未初始化的对象。 这归结为其中之一:
寻找原因
由于问题是一个Nothing
的对象引用,答案是要检查它们以找出哪一个。 然后确定它未被初始化的原因。 将鼠标放在各种变量上,Visual Studio(VS)将显示它们的值 - 罪魁祸首将是Nothing
。
您还应该从相关代码中删除任何Try / Catch块,尤其是那些Catch块中没有任何内容的代码块。 这会导致您的代码在尝试使用Nothing
的对象时崩溃。 这是你想要的,因为它可以确定问题的确切位置,并允许你识别造成它的对象。
Catch中的MsgBox
显示Error while...
将没有多大帮助。 这种方法也会导致Stack Overflow问题非常糟糕,因为您无法描述实际的异常,所涉及的对象或者代码行。
你也可以使用Locals Window
( Debug - > Windows - > Locals )来检查你的对象。
一旦你知道问题出在什么地方,通常很容易解决问题,并且比发布新问题更快。
也可以看看:
示例和补救措施
类对象/创建实例
Dim reg As CashRegister
...
TextBox1.Text = reg.Amount ' NRE
问题是Dim
不会创建一个CashRegister对象; 它只声明一个名为该类型的reg
的变量。 声明一个对象变量并创建一个实例是两件不同的事情。
补救
New
运算符通常可用于在声明它时创建实例:
Dim reg As New CashRegister ' [New] creates instance, invokes the constructor
' Longer, more explicit form:
Dim reg As CashRegister = New CashRegister
以后只适合于创建实例:
Private reg As CashRegister ' Declare
...
reg = New CashRegister() ' Create instance
注意: 不要在过程中再次使用Dim
,包括构造函数( Sub New
):
Private reg As CashRegister
'...
Public Sub New()
'...
Dim reg As New CashRegister
End Sub
这将创建一个局部变量reg
,它只存在于该上下文(sub)中。 您将在其他地方使用的模块级别为Scope
的reg
变量保持为Nothing
。
New
运算符的缺失是NullReference Exceptions
的第一个原因,在所检查的Stack Overflow问题中看到。
Visual Basic尝试使用New
重复清除该过程:使用New
Operator创建一个新对象并调用Sub New
- 构造函数 - 您的对象可以在其中执行任何其他初始化。
要明确, Dim
(或Private
)只声明一个变量及其Type
。 变量的范围 - 无论它是存在于整个模块/类还是程序本地的 - 由其声明的位置决定。 Private | Friend | Public
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>
with 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
。 没有创建名为“Employee”的DataTable
,因此尝试访问它的NullReferenceException
结果。 另一个潜在的问题是假定当SQL包含WHERE子句时,将会出现不可能出现的Items
。
补救
由于这使用一个表,所以使用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
可能是Nothing。 补救措施是一次测试一个对象的整个链或路径:
If (myFoo IsNot Nothing) AndAlso
(myFoo.Bar IsNot Nothing) AndAlso
(myFoo.Bar.Items IsNot Nothing) Then
....
AndAlso
也很重要。 一旦遇到第一个False
条件,后续测试将不会执行。 这允许代码一次安全地“钻取”到一个“级别”的对象中,只有在(和if) myFoo
被确定为有效之后才评估myFoo.Bar
。 编码复杂对象时,对象链或路径可能会变得很长:
myBase.myNodes(3).Layer.SubLayer.Foo.Files.Add("somefilename")
无法引用null
对象的任何'下游'。 这也适用于控制:
myWebBrowser.Document.GetElementById("formfld1").InnerText = "some value"
在这里, myWebBrowser
或Document
可能是Nothing,或者formfld1
元素可能不存在。
UI控件
Dim cmd5 As New SqlCommand("select Cartons, Pieces, Foobar " _
& "FROM Invoice where invoice_no = '" & _
Me.ComboBox5.SelectedItem.ToString.Trim & "' And category = '" & _
Me.ListBox1.SelectedItem.ToString.Trim & "' And item_name = '" & _
Me.ComboBox2.SelectedValue.ToString.Trim & "' And expiry_date = '" & _
Me.expiry.Text & "'", con)
除此之外,此代码不会预期用户可能没有在一个或多个UI控件中选择某些内容。 ListBox1.SelectedItem
可能是Nothing
,因此ListBox1.SelectedItem.ToString
将导致NRE。
补救
在使用它之前验证数据(也使用Option Strict
和SQL参数):
Dim expiry As DateTime ' for text date validation
If (ComboBox5.SelectedItems.Count > 0) AndAlso
(ListBox1.SelectedItems.Count > 0) AndAlso
(ComboBox2.SelectedItems.Count > 0) AndAlso
(DateTime.TryParse(expiry.Text, expiry) Then
'... do stuff
Else
MessageBox.Show(...error message...)
End If
或者,您可以使用(ComboBox5.SelectedItem IsNot Nothing) AndAlso...
Visual Basic窗体
Public Class Form1
Private NameBoxes = New TextBox(5) {Controls("TextBox1"), _
Controls("TextBox2"), Controls("TextBox3"), _
Controls("TextBox4"), Controls("TextBox5"), _
Controls("TextBox6")}
' same thing in a different format:
Private boxList As New List(Of TextBox) From {TextBox1, TextBox2, TextBox3 ...}
' Immediate NRE:
Private somevar As String = Me.Controls("TextBox1").Text
这是获得NRE的一种相当常见的方式。 在C#中,根据编码的方式,IDE将报告Controls
在当前上下文中不存在,或者“不能引用非静态成员”。 所以,在某种程度上,这是一个纯VB的情况。 它也很复杂,因为它可能导致级联失败。
数组和集合不能通过这种方式进行初始化。 此初始化代码将在构造函数创建Form
或Controls
之前运行。 结果是:
somevar
赋值将导致立即NRE,因为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没有提供警告,但补救措施是在表单级别声明容器,但在控件确实存在时,在表单加载事件处理程序中初始化它们。 只要您的代码在InitializeComponent
调用之后,就可以在Sub New
完成:
' 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
数组代码可能还没有走出困境。 Me.Controls
找不到任何位于容器控件(如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...})
函数返回Nothing
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
...
补救
使用Return bList
替换Exit Function
中的Exit Function
。 返回空List
与返回Nothing
。 如果返回的对象有可能是Nothing
,请在使用之前进行测试:
bList = myFoo.BarList()
If bList IsNot Nothing Then...
很难实施Try / Catch
严重实施的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
:从MSDN开始:
System.DBNull值表示该对象表示丢失或不存在的数据。 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 - 小心NewRow
xlWorkSheet = xlWorkBook.Sheets("sheet1")
For i = 0 To myDGV.RowCount - 1
For j = 0 To myDGV.ColumnCount - 1
For k As Integer = 1 To myDGV.Columns.Count
xlWorkSheet.Cells(1, k) = myDGV.Columns(k - 1).HeaderText
xlWorkSheet.Cells(i + 2, j + 1) = myDGV(j, i).Value.ToString()
Next
Next
Next
当DataGridView
将AllowUserToAddRows
为True
(默认值)时,底部空白/新行中的Cells
将全部包含Nothing
。 大多数尝试使用内容(例如, ToString
)都会导致NRE。
补救
使用For/Each
循环并测试IsNewRow
属性以确定它是否是最后一行。 无论AllowUserToAddRows
是否为true,这都会起作用:
For Each r As DataGridViewRow in myDGV.Rows
If r.IsNewRow = False Then
' ok to use this row
如果使用For n
循环,请修改行数或在IsNewRow
为true时使用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异常
另一种情况是将空对象转换为值类型。 例如,下面的代码:
object o = null;
DateTime d = (DateTime)o;
它会在cast上抛出一个NullReferenceException
。 在上面的示例中,这似乎很明显,但是这可能发生在更多的“迟绑定”错综复杂的场景中,其中空对象已从您不拥有的某些代码返回,并且演员例如由某个自动系统生成。
其中一个例子是带有Calendar控件的这个简单的ASP.NET绑定片段:
<asp:Calendar runat="server" SelectedDate="<%#Bind("Something")%>" />
在这里, SelectedDate
实际上是Calendar
Web Control类型的一个DateTime
类型的属性,绑定可以完全返回null。 隐式ASP.NET生成器将创建一段代码,该代码将等同于上面的演员代码。 这会引发NullReferenceException
,这是很难发现的,因为它存在于ASP.NET生成的代码中,这些代码编译得很好......