我有一些代码,当它执行时,抛出了一个 NullReferenceException
异常,显示:
对象引用未设置为对象的实例。
这是什么意思?我该如何修复此错误?
我有一些代码,当它执行时,抛出了一个 NullReferenceException
异常,显示:
对象引用未设置为对象的实例。
这是什么意思?我该如何修复此错误?
您正在尝试使用一个null
(或在VB.NET中为Nothing
)的东西。这意味着您要么将其设置为null
,要么根本没有设置它。
像其他任何东西一样,null
会被传递。如果它在方法“A”中是null
,那么可能是方法“B”向方法“A”传递了一个null
。
null
可以有不同的含义:
NullReferenceException
。null
来表示没有可用的有意义的值。请注意,C#具有可为空的数据类型概念,用于变量(例如数据库表可以有可为空的字段)- 您可以将null
分配给它们以指示其中没有存储任何值,例如int?a = null;
(这是Nullable<int> a = null;
的快捷方式),其中问号表示允许在变量a
中存储null
。您可以使用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
始终意味着同一件事:您正在尝试使用引用,但该引用未初始化(或曾经初始化,但现在未初始化)。null
,您无法通过null
引用访问成员(例如方法)。最简单的情况:string foo = null;
foo.ToUpper();
第二行将抛出 NullReferenceException
,因为您不能在指向 null
的 string
引用上调用实例方法 ToUpper()
。
如何找到 NullReferenceException
源代码?除了查看异常本身外,Visual Studio中的调试一般规则适用:放置关键断点并检查您的变量,可以通过将鼠标悬停在名称上、打开(快速)监视窗口或使用各种调试面板(例如Locals和Autos)来进行。
如果要查找引用的设置位置,请右键单击其名称并选择“查找所有引用”。然后,您可以在每个找到的位置处放置断点并带有附加调试器运行程序。每次调试器在此类断点上暂停时,您需要确定是否期望引用为非null,检查变量并验证它在您期望的时间指向一个对象实例。
通过以这种方式跟踪程序流程,可以找到实例不应为null的位置以及为什么未正确设置实例。
可能会抛出异常的一些常见情况:
ref1.ref2.ref3.member
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.
}
}
Book b1 = new Book
{
Author = { Age = 45 }
};
这翻译成:
Book b1 = new Book();
b1.Author.Age = 45;
< p > 当使用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
。集合的初始化语法并不会为 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
}
}
注意: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;
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();
如果在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
控件在调用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>
这里在创建label1
之前就已经创建了comboBox1
。如果comboBox1_SelectionChanged
试图引用`label1`,它将尚未被创建。
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
label1.Content = comboBox1.SelectedIndex.ToString(); // NullReferenceException here!!
}
更改在XAML
中声明的顺序(即,将label1
列在comboBox1
之前,忽略设计哲学问题)至少可以解决此处的NullReferenceException
。
as
进行强制类型转换var myThing = someObject as Thing;
InvalidCastException
,但在转换失败时(以及someObject
本身为null时)返回一个null
。因此请注意。
FirstOrDefault()
和 SingleOrDefault()
First()
和Single()
在没有内容时会抛出异常。 "OrDefault"版本在这种情况下返回null
。因此请注意。
null
集合时,foreach
会抛出异常。通常是由返回集合的方法意外返回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.
}
尽管此检查不会出现在您的发布版本中, 但当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。这意味着像这样的情况:
var title = person.Title.ToUpper();
ToUpper
。C# 5
及以下版本中,可以使用以下方法进行保护:var title = person.Title == null ? null : person.Title.ToUpper();
现在,标题变量将为空,而不是抛出异常。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];
并返回第i个元素。
在C#8
中引入的空上下文和可为空引用类型对变量执行静态分析,并在值可能为null
或已设置为null
时提供编译器警告。可为空的引用类型允许明确允许类型为null
。
可以使用csproj
文件中的Nullable
元素为项目设置可空性注释上下文和可空性警告上下文。此元素配置编译器如何解释类型的可空性以及生成哪些警告。有效设置为:
enable
: 可空注释上下文已启用。可空警告上下文已启用。引用类型变量(例如字符串)是非空的。所有空值警告都已启用。disable
: 可空注释上下文已禁用。可空警告上下文已禁用。引用类型变量是无意识的,就像早期版本的C#一样。所有空值警告都已禁用。safeonly
: 可空注释上下文已启用。可空警告上下文已设置为仅安全。引用类型变量是非空的。所有安全空值警告都已启用。warnings
: 可空注释上下文已禁用。可空警告上下文已启用。引用类型变量是无意识的。所有空值警告都已启用。safeonlywarnings
: 可空注释上下文已禁用。可空警告上下文已设置为仅安全。引用类型变量是无意识的。所有安全空值警告都已启用。可空引用类型使用与可空值类型相同的语法来标记:在变量类型后添加?
。
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
到对象的参考源代码,就会看到这种技术被广泛使用。它写起来略微笨重,但可以更轻松地调试空值错误。优化您的代码以方便调用者,而不是作者的方便。C#
有一种"不安全"模式,正如其名称所示,非常危险,因为不执行提供内存安全性和类型安全性的正常安全机制。除非您对内存工作原理有深入彻底的了解,否则不应编写不安全代码。NullReferenceException
。(这些细节适用于在Windows上运行的.NET;其他操作系统使用类似的机制。)new Book { Author = { Age = 45 } };
内部初始化是如何工作的...我想不出任何内部初始化会起作用的情况,但它可以编译并且 IntelliSense 也能正常工作...除非是对于结构体? - user1228NullReference Exception
(空引用异常)在 Visual Basic 中和在 C# 中没有区别。毕竟,它们都报告了由它们共同使用的.NET Framework定义的相同异常。唯一与Visual Basic相关的原因很少见(也许只有一个)。
本答案将使用Visual Basic术语、语法和上下文。所使用的示例来自于大量过去的Stack Overflow问题。这是为了通过使用通常在帖子中出现的类型的情况最大限度地提高相关性。对于那些可能需要的人,还提供了更多的解释。类似于您的示例非常可能在此处列出。
注意:
NullReferenceException
(NRE),如何找到它,如何修复它以及如何避免它。 NRE可能会以多种方式引起,因此这不太可能是您唯一的遭遇。消息"Object not set to an instance of Object"(对象未设置为对象的实例)意味着您正在尝试使用未初始化的对象。这可以归结为以下几点:
由于问题是一个引用为Nothing
的对象,因此答案是检查它们以找出哪一个。然后确定为什么它没有被初始化。将鼠标悬停在各个变量上,Visual Studio(VS)将显示它们的值——罪魁祸首将是Nothing
。
您还应该从相关代码中删除任何Try/Catch块,特别是其中Catch块中没有任何内容的情况。这将导致您的代码在尝试使用一个为Nothing
的对象时崩溃。 这正是您想要的,因为它将确定问题的确切位置,并允许您确定导致它的对象。
在Catch中显示Error while...
的MsgBox
将没有太大帮助。此方法还会导致非常糟糕的Stack Overflow问题,因为您无法描述实际异常、涉及的对象甚至发生它的代码行。
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
Private reg As CashRegister
'...
Public Sub New()
'...
Dim reg As New CashRegister
End Sub
reg
,仅存在于该上下文(子函数)中。模块级别Scope
的reg
变量在其他任何地方都将保持为Nothing
。
需要明确的是,在审查的Stack Overflow问题中,缺少
New
运算符是空引用异常的首要原因。Visual Basic尝试使用
New
反复澄清这个过程:使用New
运算符创建一个新对象,调用Sub New
——构造函数——在这里,您的对象可以执行任何其他初始化。
Dim
(或Private
)只是声明一个变量及其类型。变量的作用域——无论它是否存在于整个模块/类中,或者仅存在于过程中——由声明位置确定。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)
类。
与数据库一起工作会出现许多空引用的机会,因为可以同时使用许多对象(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
。另一个潜在的问题是假设将存在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
是一个返回受影响的 行数
的函数,可以进行测试:
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
条件,后续测试将不会执行。这使得代码可以安全地一次又一次地 "钻取" 对象的 "级别",只有在确定 myFoo
有效之后(如果确实有效),才会评估 myFoo.Bar
。在编写复杂对象时,对象链或路径可能会变得非常长:
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
Controls
不存在于当前上下文中,或者“无法引用非静态成员”。因此,在某种程度上,这是仅限于VB的情况。它也很复杂,因为它可能导致故障级联。Form
或Controls
之前运行。结果是:somevar
赋值将立即导致NRE,因为Nothing没有.Text
属性Form_Load
中这样做,由于一个奇怪的错误,IDE在发生异常时可能不会报告异常。当你的代码尝试使用数组时,异常将弹出。这种“静默异常”在这篇文章中详细说明。对于我们的目的,关键是当创建表单(Sub New
或Form Load
事件)时发生灾难性事件时,异常可能不会报告,代码退出过程并仅显示表单。Sub New
或Form Load
事件中没有其他代码运行,因此许多其他事情可能会保持未初始化状态。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
GroupBox
或Panel
)都不会在Me.Controls
中找到;它们将在该面板或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
'。您可以通过用Return Nothing
替换Exit Function
来抑制警告,但这并不能解决问题。当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
For Each row As DataGridViewRow In dgvPlanning.Rows
If Not IsDBNull(row.Cells(0).Value) Then
...
IsDBNull
函数用于测试一个值是否等于System.DBNull
:来自MSDN:
System.DBNull的值表示对象代表缺失或不存在的数据。DBNull与Nothing不同,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
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还是false,此方法都适用: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
项可能会导致第一次使用时出现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
。
另一种情况是当你将一个null对象转换为值类型时。例如,下面的代码:
object o = null;
DateTime d = (DateTime)o;
进行强制类型转换时将抛出NullReferenceException
异常。在上面的示例中,这似乎非常明显,但是在更多“后期绑定”复杂场景中可能会发生这种情况,其中空对象已从某些您不拥有的代码返回,并且类型转换由某些自动系统生成。
这种情况的一个例子是使用日历控件的简单ASP.NET绑定片段:
<asp:Calendar runat="server" SelectedDate="<%#Bind("Something")%>" />
这里,SelectedDate
实际上是 Calendar
Web 控件类型的一个 DateTime
类型属性,并且绑定可以完全返回 null。ASP.NET 隐式生成器将创建一段代码,该代码将等同于上面的转换代码。这将引发一个非常难以发现的 NullReferenceException
,因为它位于 ASP.NET 生成的代码中,而该代码可以编译通过...
DateTime x = (DateTime) o as DateTime? ?? defaultValue;
- Serge Shultz这意味着你的代码使用了一个被设置为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
}
这意味着变量指向了空值。可以像下面这样生成:
SqlConnection connection = null;
connection.Open();
那会抛出错误,因为虽然我已经声明了变量“connection
”,但它没有指向任何东西。当我尝试调用成员“Open
”时,没有引用来解析它,它将抛出该错误。
为避免此错误:
object == null
进行检查。JetBrains 的 ReSharper 工具将识别代码中潜在的空引用错误,并允许您添加空值检查。在我看来,这种错误是导致错误的主要来源。
请注意,无论情况如何,在.NET中造成问题的原因始终相同:
您正在尝试使用一个引用变量,其值为
Nothing
/null
。当引用变量的值为Nothing
/null
时,这意味着它实际上未持有对堆上存在的任何对象实例的引用。要么您从未为变量分配过值,从未创建过指定给变量的值的实例,或者手动将变量设置为
Nothing
/null
,或者调用了一个将该变量设置为Nothing
/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#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,了解 值类型 和[引用类型][3]的区别非常重要。另一个可能发生NullReferenceExceptions
的情况是(错误地)使用as
运算符:
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
ComicBook cb = (ComicBook)specificBook;
as
:ComicBook cb = specificBook as ComicBook;
if (cb != null) {
// ...
}
string value = null;
if (value.Length == 0) // <-- Causes exception
{
Console.WriteLine(value); // <-- Never reached
}
未处理异常:
System.NullReferenceException:对象引用未设置实例的值。在Program.Main()处。