什么是NullReReference ceException,如何修复它?

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

对象引用未设置为对象的实例。

这是什么意思,我能做些什么来修复这个错误?

1992138 次浏览

这意味着您的代码使用了设置为null的对象引用变量(即它没有引用实际的对象实例)。

为了防止错误,在使用之前应该测试可能为null的对象是否为null。

if (myvar != null){// Go ahead and use myvarmyvar.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”时,没有引用供它解析,它会抛出错误。

要避免此错误:

  1. 在尝试对对象做任何事情之前,始终初始化它们。
  2. 如果您不确定对象是否为空,请使用object == null检查它。

JetBrains的ReSharper工具将识别代码中可能出现空引用错误的每个位置,允许您进行空检查。这个错误是错误的头号来源,IMHO。

病因是什么?

底线

您正在尝试使用null(或VB.NET中的Nothing)。这意味着您要么将其设置为null,要么根本不将其设置为任何内容。

像其他任何东西一样,null被传递。如果是null方法“A”,则可能是方法“B”传递了null方法“A”。

null可以有不同的含义:

  1. 对象变量未初始化,因此指向无。在这种情况下,如果您访问此类对象的成员,它会导致NullReferenceException
  2. 开发人员是null9请注意,C#具有变量可为空数据类型的概念(例如数据库表可以具有可为空的字段)-您可以将null分配给它们以表示其中没有存储任何值,例如int? a = null;(这是Nullable<int> a = null;的快捷方式),其中问号表示允许将null存储在变量a中。您可以使用if (a.HasValue) {...}if (a==null) {...}进行检查。可为空的变量,像a这个例子,允许通过a.Value显式访问值,或者通过a正常访问。
    int? a = null;0通过a.Value访问它会抛出一个null2而不是null3,如果anull-你应该事先做检查,即如果你有另一个不可为空的变量null6,那么你应该做像null7或更短的null8这样的赋值。

本文的其余部分将更详细地介绍并展示许多程序员经常犯的可能导致NullReferenceException的错误。

更具体地说

runtime抛出NullReferenceException总是意味着同样的事情:您正在尝试使用引用,并且引用未初始化(或者它被一次初始化,但被不再初始化)。

这意味着引用是null,您不能通过null引用访问成员(例如方法)。最简单的情况:

string foo = null;foo.ToUpper();

这将在第二行抛出NullReferenceException,因为您无法在指向nullstring引用上调用实例方法ToUpper()

调试

你如何找到NullReferenceException的来源?除了查看异常本身,它将准确地抛出它发生的位置,Visual Studio中调试的一般规则适用:放置战略断点和检查变量,通过将鼠标悬停在它们的名称上,打开(快速)观察窗口或使用各种调试面板,如本地和汽车。

如果您想找出引用的设置位置,请右键单击其名称并选择“查找所有引用”。然后,您可以在每个找到的位置放置一个断点,并在附加调试器的情况下运行您的程序。每次调试器在这样的断点上中断时,您都需要确定您是否期望引用为非空,检查变量,并验证它是否在您期望的时候指向实例。

通过以这种方式遵循程序流程,您可以找到实例不应该为空的位置,以及为什么没有正确设置它。

示例

可以抛出异常的一些常见场景:

通用

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)空引用,您可以在父(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 clickedmyIssue = 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 NullReferenceExceptionstring firstName = Session["FirstName"].ToString();

ASP.NETMVC空视图模型

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

// Controllerpublic 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,而是在转换失败时(当someObject本身为null时)返回null。所以要注意这一点。

LINQFirstOrDefault()SingleOrDefault()

普通版本First()Single()在没有异常时抛出异常。在这种情况下,“OrDefault”版本返回null。所以要注意这一点。

Foreach

当您尝试迭代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 nullif (book == null)throw new BookNotFoundException(bookTitle);  // Your custom exceptionreturn 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.NET14中可用):

这有时也称为安全导航或埃尔维斯(以其形状命名)运算符。如果运算符左侧的表达式为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,如果person.Titlenull,则不会调用ToUpper

当然,仍然必须选中title以获取null,或者使用null条件运算符和null合并运算符(??)来提供默认值:

// regular null checkint titleLength = 0;if (title != null)titleLength = title.Length; // If title is null, this would throw NullReferenceException    
// combining the `?` and the `??` operatorint titleLength = title?.Length ?? 0;

同样,对于数组,您可以使用?[i]如下:

int[] myIntArray = null;var i = 5;int? elem = myIntArray?[i];if (!elem.HasValue) Console.WriteLine("No value");

这将执行以下操作:如果myIntArraynull,表达式返回null,您可以安全地检查它。如果它包含一个数组,它将执行与:elem = myIntArray[i];并返回ith元素。

使用空上下文(在C#8中可用):

C# 8中引入的null上下文和可为空的引用类型对变量执行静态分析,并在值可能为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 THISpublic 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 THISpublic 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 methodDebug.Assert(f != null);for (int i = 0; i < count; ++i)yield return f.MakeFrob();}

也就是说,创建一个具有迭代器块逻辑的私有helper方法和一个执行null检查并返回迭代器的公共表面方法。现在,当调用GetFrobs时,null检查立即发生,然后GetFrobsForReal在迭代序列时执行。

如果您检查LINQ to Object的参考源,您将看到自始至终都使用了这种技术。编写起来稍微笨重一些,但它使调试无效错误变得容易得多。为了调用者的方便而优化代码,而不是作者的方便

关于不安全代码中空取消引用的说明

C#有一个“不安全”模式,顾名思义,这是非常危险的,因为提供内存安全和类型安全的正常安全机制没有强制执行。你不应该写不安全的代码,除非你对内存的工作原理有深入的了解

在不安全模式下,您应该了解两个重要事实:

  • 取消引用null指针会产生与取消引用null参考相同的异常
  • 取消引用无效的非空指针可以在某些情况下会产生异常

要理解为什么会这样,首先要了解. NET如何生成NullReferenceException。(这些细节适用于在Windows上运行的. NET;其他操作系统使用类似的机制。)

内存在Windows中被虚拟化;每个进程都有一个由操作系统跟踪的许多内存“页面”组成的虚拟内存空间。每个内存页面都设置了标志,决定了它的使用方式:读取、写入、执行等。最低页被标记为“如果以任何方式使用,会产生错误”。

C#中的空指针和空引用在内部都表示为数字零,因此任何将其解引用到其相应内存存储中的尝试都会导致操作系统产生错误。NET运行时然后检测到此错误并将其转换为NullReferenceException

这就是为什么取消引用空指针和空引用会产生相同的异常。

第二点呢?取消引用位于虚拟内存最低页的任何无效指针会导致相同的操作系统误差,从而导致相同的异常。

为什么这有意义?假设我们有一个包含两个int的结构,以及一个等于null的非托管指针。如果我们尝试取消引用结构中的第二个int,CLR将不会尝试访问位置零的存储;它将访问位置四的存储。但从逻辑上讲,这是一个空取消引用,因为我们正在访问地址通过 null。

如果您正在使用不安全的代码并且您得到了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运行时将抛出NullRe的异常。

与ArgumentNullException相比,如果方法期望传递给它的内容不为空,则通常会作为防御措施抛出。

更多信息在C#NullRe的异常和空参数

另一种情况是将空对象强制转换为值类型。例如,下面的代码:

object o = null;DateTime d = (DateTime)o;

它将在强制转换上抛出NullReferenceException。这在上面的示例中似乎很明显,但这可能发生在更“后期绑定”的复杂场景中,其中空对象已从您不拥有的某些代码中返回,并且强制转换例如由某些自动系统生成。

其中一个例子是这个带有日历控件的简单ASP.NET绑定片段:

<asp:Calendar runat="server" SelectedDate="<%#Bind("Something")%>" />

在这里,SelectedDate实际上是Calendar Web Control类型的DateTime类型的属性,绑定可以完美地返回null。隐式ASP.NET生成器将创建一段与上面的转换代码等效的代码。这将引发一个很难发现的NullReferenceException,因为它位于ASP.NET生成的代码中,编译良好…

您正在使用包含空值引用的对象。所以它给出了一个null异常。在示例中,字符串值为null,并且在检查其长度时,会发生异常。

示例:

string value = null;if (value.Length == 0) // <-- Causes exception{Console.WriteLine(value); // <-- Never reached}

异常错误是:

未处理的异常:

System. NullReReference ceException:对象引用未设置为实例对象。在Program. Main()

NullReferenceExceptions可能发生的另一种情况是(不正确)使用#1操作员

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

这里,BookCar是不兼容的类型;Car不能转换/转换为Book。当此转换失败时,as返回null。在此之后使用mybook会导致NullReferenceException

通常,您应该使用强制转换或as,如下所示:

如果你期望类型转换总是成功的(例如,你知道对象应该提前是什么),那么你应该使用强制转换:

ComicBook cb = (ComicBook)specificBook;

如果您不确定类型,但希望尝试将其用作特定类型,请使用as

ComicBook cb = specificBook as ComicBook;if (cb != null) {// ...}

更新C#8.0,2019:可为空的引用类型

C#8.0引入了可为空引用类型不可为空的引用类型。因此必须只检查可为空的引用类型以避免空引用异常


如果您还没有初始化引用类型,并且您想设置或读取它的一个属性,它将抛出空引用异常

示例:

Person p = null;p.Name = "Harry"; // NullReferenceException occurs here.

您可以通过检查变量是否为null来简单地避免这种情况:

Person p = null;if (p!=null){p.Name = "Harry"; // Not going to run to this point}

要完全理解为什么会抛出NullReReference ceException,重要的是要知道值类型和[引用类型][3]之间的区别。

因此,如果您处理的是值类型,则可能会发生没有。尽管您在处理引用类型时需要保持警惕!

顾名思义,只有引用类型可以保存引用或从字面上指向任何东西(或“null”)。而值类型总是包含一个值。

引用类型(必须检查这些类型):

  • 动态
  • 对象
  • 字符串

值类型(您可以简单地忽略这些):

  • 数字类型
  • 整数类型
  • 浮点类型
  • 十进制
  • bool
  • 用户定义结构

另一种可能收到此异常的一般情况涉及在单元测试期间模拟类。无论使用何种模拟框架,您都必须确保类层次结构的所有适当级别都被正确模拟。特别是,被测代码引用的HttpContext的所有属性都必须被模拟。

请参阅“测试自定义AuthorizationAt的时候抛出NullRe的异常”以获取稍微冗长的示例。

当实体框架中使用的实体类名与Web表单代码隐藏文件的类名相同时添加一个案例。

假设您有一个Web表单Contact.aspx其代码隐藏类是联系,并且您有一个实体名称联系。

然后下面的代码将在调用Context. Save更改()时抛出一个NullReReference ceException

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

和联系实体类。有时实体类是部分类,以便您也可以在其他文件中扩展它们。

public partial class Contact{public string Name {get; set;}}

当实体和代码隐藏类位于同一命名空间时,会发生错误。要解决这个问题,重命名实体类或代码隐藏类Contact.aspx.

<强>原因我仍然不确定原因。但是每当任何实体类将扩展System.Web.UI.页面时,都会发生此错误。

为了讨论,看看DbContext.save更改()

虽然什么导致空引用异常避免/修复这样的异常的方法已经在其他答案中得到解决,但许多程序员还没有学会如何在开发过程中独立地调试这样的异常。

在Visual Studio中,由于Visual Studio调试器,这通常很容易。


首先,确保正确的错误将被捕获-请参阅如何在VS2010中允许中断“System. NullReReference ceException”?注释1

然后是从调试开始(F5)将[VS调试器]附加到正在运行的进程。有时使用#0可能很有用,这将提示启动调试器。

现在,当抛出(或未处理)NullReReference ceException时,调试器将在发生异常的行上停止(还记得上面设置的规则吗?)。有时错误很容易发现。

例如,在下面的行中,可以导致异常的唯一代码是myString的计算结果是否为null。这可以通过查看监视窗口或运行即时窗口中的表达式来验证。

var x = myString.Trim();

在更高级的情况下,例如以下情况,您需要使用上述技术之一(Watch或立即Windows)来检查表达式以确定str1是否为null或str2是否为null。

var x = str1.Trim() + str2.Trim();

一旦在哪里异常被抛出,向后推理以找出空值被[错误地]引入的位置通常是微不足道的-

花时间了解异常的原因。检查空表达式。检查以前可能导致此类空表达式的表达式。添加断点并根据需要逐步执行程序。使用调试器。


1如果中断抛出过于激进,并且调试器停止在. NET或第三方库中的NPE上,用户未处理的中断可用于限制捕获的异常。此外,VS2012引入了只是我的代码,我建议也启用它。

如果您在启用Just My Code的情况下调试,行为会略有不同。启用Just My Code后,调试器会忽略在My Code之外抛出的第一次机会公共语言运行时(CLR)异常,这些异常不会通过My Code

当我们试图访问空对象的属性或当字符串值变为空并且我们试图访问字符串方法时,会抛出NullReferenceException

例如:

  1. 当访问空字符串的字符串方法时:

    string str = string.Empty;str.ToLower(); // throw null reference exception
  2. When a property of a null object accessed:

    Public Class Person {public string Name { get; set; }}Person objPerson;objPerson.Name  /// throw Null refernce Exception

我对回答这个问题有不同的看法。这种答案”我还能做些什么来避免它吗?

当工作跨越不同的层时,例如在MVC应用程序中,控制器需要服务来调用商业运营。在这种情况下,依赖注入容器可用于初始化服务以避免空引用异常。这意味着你不需要担心检查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();}}

Simon Mourier举了个例子

object o = null;DateTime d = (DateTime)o;  // NullReferenceException

其中拆箱转换(转换)object(或来自类System.ValueTypeSystem.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<>实例时使用的特殊规则。

NullReference异常-Visual Basic

Visual BasicNullReference Exceptionc#中的NullReference Exception没有什么不同。毕竟,它们都报告了它们都使用的. NET Framework中定义的相同异常。Visual Basic独有的原因很少(也许只有一个)。

这个答案将使用Visual Basic术语、语法和上下文。使用的示例来自大量过去的Stack Overflow问题。这是为了通过使用帖子中经常看到的情况来最大化相关性。还为可能需要的人提供了更多的解释。这里可能列出了一个类似于你的例子非常

备注:

  1. 这是基于概念的:没有代码可供您粘贴到您的项目中。它旨在帮助您了解导致NullReferenceException(NRE)的原因、如何找到它、如何修复它以及如何避免它。NRE可以通过多种方式引起,因此这不太可能是您唯一的遇到。
  2. 示例(来自Stack Overflow帖子)并不总是首先展示做某事的最佳方法。
  3. 通常,使用最简单的补救措施。

基本含义

消息“对象未设置为对象的实例”意味着您正在尝试使用尚未初始化的对象。这归结为其中之一:

  • 您的代码宣布一个对象变量,但它没有初始化它(创建一个实例或实例化它)
  • 你的代码假设会初始化一个对象的东西,没有
  • 可能是其他代码过早地使仍在使用的对象无效

寻找原因

由于问题是一个对象引用Nothing,答案是检查它们以找出哪一个。然后确定它没有初始化的原因。将鼠标放在各种变量上,Visual Studio(VS)将显示它们的值-罪魁祸首将是Nothing

IDE调试显示

您还应该从相关代码中删除任何尝试/捕获块,尤其是Catch块中没有任何内容的块。这将导致您的代码在尝试使用Nothing.这就是你想要的对象时崩溃,因为它将识别问题的确切位置,并允许您识别导致问题的对象。

Catch中显示Error while...MsgBox将没有什么帮助。这种方法还会导致非常糟糕 Stack Overflow问题,因为您无法描述实际的异常、涉及的对象甚至发生异常的代码行。

您还可以使用Locals Window调试->Windows->本地)来检查您的对象。

一旦你知道问题是什么,在哪里,它通常是相当容易修复,比发布一个新问题更快。

另见:

实例和补救措施

类对象/创建实例

Dim reg As CashRegister...TextBox1.Text = reg.Amount         ' NRE

问题是Dim没有创建CashList对象;它只声明了一个名为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 CashRegisterEnd Sub

这将创建一个当地变量reg,它只存在于该上下文(子)中。您将在其他任何地方使用的模块级别Scopereg变量仍然是Nothing

缺少#0运算符是#1的#1原因在审查的Stack Overflow问题中看到。

Visual Basic尝试使用#0反复明确流程:使用#0运算符创建一个新的对象并调用#2——构造函数——您的对象可以在其中执行任何其他初始化。

需要明确的是,Dim(或Private)只有宣布一个变量和它的Type。变量的范围——它是否存在于整个模块/类中,或者是过程的本地——由在哪里确定它被声明。Private | Friend | Public定义访问级别,而不是范围

有关更多信息,请参阅:


数组

数组也必须实例化:

Private arr as String()

此数组仅被声明,未创建。有几种方法可以初始化数组:

Private arr as String() = New String(10){}' orPrivate 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 - 1arrFoo(i).Bar = i * 10       ' ExceptionNext

数组已创建,但其中的Foo对象尚未创建。

补救措施

For i As Integer = 0 To arrFoo.Count - 1arrFoo(i) = New Foo()         ' Create Foo instancearrFoo(i).Bar = i * 10Next

使用List(Of T)将使没有有效对象的元素变得非常困难:

Dim FooList As New List(Of Foo)     ' List created, but it is emptyDim f As Foo                        ' Temporary variable for the loop
For i As Integer = 0 To 5f = New Foo()                    ' Foo instance createdf.Bar =  i * 10FooList.Add(f)                   ' Foo object added to listNext

有关更多信息,请参阅:


列表和集合

. 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 FooPrivate barList As List(Of Bar)
Friend Function BarCount As IntegerReturn barList.CountEnd Function
Friend Sub AddItem(newBar As Bar)If barList.Contains(newBar) = False ThenbarList.Add(newBar)End IfEnd 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 procedureDim barList As New List(Of Bar)End Sub

有关更多信息,请参阅#0类


数据提供者对象

使用数据库为NullReference提供了许多机会,因为可以同时使用许多对象(CommandConnectionTransactionDatasetDataTableDataRows……)。

例1

Dim da As OleDbDataAdapterDim ds As DataSetDim 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警告你可能会发生这种情况:

img

当声明为模块/类级别的变量时,就像con的情况一样,编译器无法知道对象是否是由上游过程创建的。不要忽略警告。

补救措施

Dim ds As New DataSet

例2

ds = New DataSetda = 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 vsEmployee。没有创建名为“员工”的DataTable,因此尝试访问它会导致NullReferenceException。另一个潜在的问题是假设会有Items,当SQL包含WHERE子句时可能不是这样。

补救措施

由于这使用一个表,使用Tables(0)将避免拼写错误。检查Rows.Count也可以帮助:

If ds.Tables(0).Rows.Count > 0 ThentxtID.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 JOINFLIGHT_DETAILS AS FLIGHT ... WHERE [TICKET.TICKET_NO]= ...", con)Dim ds As New DataSetda.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

另见数据表类


对象路径/嵌套

If myFoo.Bar.Items IsNot Nothing Then...

代码只测试Items,而myFooBar也可能都不是。补救是一次测试一个对象的整个链或路径:

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"

这里,myWebBrowserDocument可以是无,或者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 validationIf (ComboBox5.SelectedItems.Count > 0) AndAlso(ListBox1.SelectedItems.Count > 0) AndAlso(ComboBox2.SelectedItems.Count > 0) AndAlso(DateTime.TryParse(expiry.Text, expiry) Then
'... do stuffElseMessageBox.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的情况。它也很复杂,因为它可能导致故障级联。

数组和集合不能以这种方式初始化。此初始化代码将运行之前,构造函数创建FormControls。结果:

  • 列表和集合将为空
  • 数组将包含无的五个元素
  • somevar赋值将导致立即的NRE,因为没有什么没有.Text属性

稍后引用数组元素将导致NRE。如果您在Form_Load中这样做,由于奇怪的bug,IDE可能不会会在发生时报告异常。当您的代码尝试使用数组时,异常将弹出后来。这个“沉默异常”是在这篇文章中详细。出于我们的目的,关键是当创建表单(Sub NewForm Load事件)时发生灾难性事件时,异常可能会不报告,代码退出过程并仅显示表单。

由于在NRE之后不会运行Sub NewForm 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 declarationPrivate 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

数组代码可能还没有脱离困境。容器控件中的任何控件(如GroupBoxPanel)都不会在Me.Controls中找到;它们将在该面板或GroupBox的控件集合中。当控件名称拼写错误("TeStBox2")时,也不会返回控件。在这种情况下,Nothing将再次存储在这些数组元素中,并且当您尝试引用它时将导致NRE。

这些应该很容易找到,现在你知道你在找什么:VS告诉你错误的方法

“Button2”驻留在Panel

补救措施

而不是使用表单的Controls集合按名称间接引用,请使用控件引用:

' DeclarationPrivate NameBoxes As TextBox()
' Initialization -  simple and easy to read, hard to botch:NameBoxes = New TextBox() {TextBox1, TextBox2, ...)
' Initialize a ListNamesList = New List(Of TextBox)({TextBox1, TextBox2, TextBox3...})' orNamesList = 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.ClearIf someCondition ThenFor n As Integer = 0 to someValuebars.Add(GetBar(n))Next nElseExit FunctionEnd If
Return barsEnd Function

在这种情况下,IDE会警告您“并非所有路径都返回值,可能会导致#0”。您可以通过将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 SqlDataReaderTryDim 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") = eidsqlQry = "select FirstName, Surname, DepartmentName, ExtensionName, jobTitle,Pager, mailaddress, from employees1 where username='" & eid & "'"If connection.State <> ConnectionState.Open Thenconnection.Open()End Ifcommand = New SqlCommand(sqlQry, connection)
'More code fooing and barring
dr = command.ExecuteReader()If dr.Read() ThenlblFirstName.Text = Convert.ToString(dr("FirstName"))...End Ifmpe.Show()Catch
Finallycommand.Dispose()dr.Close()             ' <-- NREconnection.Close()End Try

这是未按预期创建对象的情况,但也演示了空Catch的计数器有用性。

SQL中有一个额外的逗号(在“mail地址”之后)导致.ExecuteReader处的异常。在Catch什么都不做之后,Finally尝试执行清理,但由于您无法CloseDataReader对象,因此会产生一个全新的NullReferenceException结果。

一个空的0号区块就是魔鬼的游乐场。这个OP不明白为什么他会在1号区块得到NRE。在其他情况下,一个空的0号区块可能会导致下游的其他东西失控,并导致你花时间在问题的错误地方查看错误的东西。(上面描述的“沉默例外”提供了同样的娱乐价值。)

补救措施

不要使用空的尝试/捕获块——让代码崩溃,这样你就可以a)识别原因b)识别位置和c)应用适当的补救措施。尝试/捕获块不是为了向唯一有资格修复它们的人——开发人员——隐藏异常。


DBNull不等于无

For Each row As DataGridViewRow In dgvPlanning.RowsIf Not IsDBNull(row.Cells(0).Value) Then...

IsDBNull函数用于测试是否等于System.DBNull从MSDN:

System.DBN的ull值表示Object表示丢失或不存在的数据。DBNull与无不同,这表明变量尚未初始化。

补救措施

If row.Cells(0) IsNot Nothing Then ...

和以前一样,您可以测试无,然后测试特定值:

If (row.Cells(0) IsNot Nothing) AndAlso (IsDBNull(row.Cells(0).Value) = False) Then

例2

Dim getFoo = (From f In dbContext.FooBarsWhere f.something = somethingSelect f).FirstOrDefault
If Not IsDBNull(getFoo) ThenIf IsDBNull(getFoo.user_id) ThentxtFirst.Text = getFoo.first_nameElse...

FirstOrDefault返回第一项或默认值,对于引用类型为Nothing,从不为DBNull

If getFoo IsNot Nothing Then...

控件

Dim chk As CheckBox
chk = CType(Me.Controls(chkName), CheckBox)If chk.Checked ThenReturn chkEnd If

如果找不到带有chkNameCheckBox(或存在于GroupBox中),则chk将为无,并且尝试引用任何属性将导致异常。

补救措施

If (chk IsNot Nothing) AndAlso (chk.Checked) Then ...

数据网格视图

DGV有几个定期出现的怪癖:

dgvBooks.DataSource = loan.BooksdgvBooks.Columns("ISBN").Visible = True       ' NullReferenceExceptiondgvBooks.Columns("Title").DefaultCellStyle.Format = "C"dgvBooks.Columns("Author").DefaultCellStyle.Format = "C"dgvBooks.Columns("Price").DefaultCellStyle.Format = "C"

如果dgvBooksAutoGenerateColumns = True,它将创建列,但不会命名它们,因此上述代码在按名称引用它们时失败。

补救措施

手动命名列,或按索引引用:

dgvBooks.Columns(0).Visible = True

示例2-小心NewRow

xlWorkSheet = xlWorkBook.Sheets("sheet1")
For i = 0 To myDGV.RowCount - 1For j = 0 To myDGV.ColumnCount - 1For k As Integer = 1 To myDGV.Columns.CountxlWorkSheet.Cells(1, k) = myDGV.Columns(k - 1).HeaderTextxlWorkSheet.Cells(i + 2, j + 1) = myDGV(j, i).Value.ToString()NextNextNext

当您的DataGridViewAllowUserToAddRows作为True(默认值)时,底部空白/新行中的Cells都将包含Nothing。大多数尝试使用内容(例如,ToString)将导致NRE。

补救措施

使用For/Each循环并测试IsNewRow属性以确定它是否是最后一行。这适用于AllowUserToAddRows是否为真:

For Each r As DataGridViewRow in myDGV.RowsIf r.IsNewRow = False Then' ok to use this row

如果您确实使用For n循环,请修改行数或在IsNewRow为true时使用Exit For


我的设置(StringCollection)

在某些情况下,尝试使用My.Settings中的StringCollection项可能会在第一次使用时导致NullReference。解决方案是相同的,但不那么明显。考虑:

My.Settings.FooBars.Add("ziggy")         ' foobars is a string collection

由于VB正在为您管理设置,因此可以合理地期望它初始化集合。它会,但前提是您之前已经向集合添加了初始条目(在设置编辑器中)。由于集合(显然)在添加项目时初始化,因此当设置编辑器中没有要添加的项目时,它保持Nothing

补救措施

如果需要,在表单的Load事件处理程序中初始化设置集合:

If My.Settings.FooBars Is Nothing ThenMy.Settings.FooBars = New System.Collections.Specialized.StringCollectionEnd If

通常,Settings集合只需要在应用程序第一次运行时初始化。另一种补救措施是在项目->设置|足球中向集合添加初始值,保存项目,然后删除假值。


核心进展

您可能忘记了New操作符。

您认为可以完美执行以将初始化对象返回到您的代码的东西没有。

不要忽略编译器警告(曾经)并使用Option Strict On(总是)。


MSDN空引用异常

关于"对此我该怎么办"的问题,可以有很多答案。

防止这种错误条件在开发的更“正式”的方法是在代码中应用合同设计。这意味着您需要在开发时在系统上设置类不变量和/或甚至函数/方法先决条件后置条件

简而言之,类不变量确保你的类中会有一些在正常使用中不会被违反的约束(因此,类将没有处于不一致状态)。先决条件意味着作为函数/方法输入的数据必须遵循一些约束集,从未违反它们,后置条件意味着函数/方法输出必须再次遵循设置的约束而不会违反它们。在执行无bug程序时,应该违反合同条件从未,因此在调试模式下实际检查合同设计,同时版本中禁用,以最大限度地提高开发的系统性能。

通过这种方式,您可以避免违反约束集的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);//...}

因此,代码契约项目存在于. NET应用程序中。

或者,可以使用断言应用合同设计。

更新:值得一提的是,这个词是由Bertrand Meyer与他设计的Eiffel编程语言有关创造的。

太长别读:尝试使用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

调试显示模型在我的其他视图中为空。直到我将其更改为:

@{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误读了&符号和括号。

简单来说就是:

您正在尝试访问未创建或当前不在内存中的对象。

那么如何解决这个问题:

  1. 调试并让调试器中断…它会直接带你到损坏的变量…现在你的任务是简单地修复这个…在适当的地方使用new关键字。

  2. 如果它是由一些数据库命令引起的,因为对象不存在,那么你需要做的就是做一个空检查并处理它:

    if (i == null) {// Handle this}
  3. The hardest one .. if the GC collected the object already... This generally occurs if you are trying to find an object using strings... That is, finding it by name of the object then it may happen that the GC might already cleaned it up... This is hard to find and will become quite a problem... A better way to tackle this is do null checks wherever necessary during the development process. This will save you a lot of time.

By finding by name I mean some framework allow you to FIndObjects using strings and the code might look like this: FindObject("ObjectName");

你能做些什么呢?

这里有很多很好的答案解释了什么是空引用以及如何调试它。但是关于如何防止这个问题或至少使它更容易被捕获的内容很少。

检查参数

例如,方法可以检查不同的参数以查看它们是否为空并抛出ArgumentNullException,这显然是为此目的而创建的异常。

ArgumentNullException的构造函数甚至将参数的名称和消息作为参数,以便您可以准确地告诉开发人员问题是什么。

public void DoSomething(MyObject obj) {if(obj == null){throw new ArgumentNullException("obj", "Need a reference to obj.");}}

使用工具

还有几个库可以提供帮助。例如,“Resharper”可以在您编写代码时为您提供警告,特别是如果您使用它们的属性:属性类型

有“Microsoft代码合约”,您使用Contract.Requires(obj != null)之类的语法,它为您提供运行时和编译检查:引入代码合约

还有“PostSharp”,它允许您只使用这样的属性:

public void DoSometing([NotNull] obj)

这样做并使PostSharp成为构建过程的一部分obj将在运行时检查是否为null。请参阅:PostSharp空检查

纯代码解决方案

或者你可以始终使用普通的旧代码编写自己的方法。例如,这是一个你可以用来捕获空引用的结构体。它以与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 exceptionNotNull<Person> person = new Person(); // OKNotNull<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);}

正如你在上面看到的,与null able一样,你将通过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的属性,依此类推。如果countryStateCountyCitynull,那么address will benull. Therefore you only have to check whether地址Country0null'。

这是一个很棒的特性,但它提供的信息较少。它并不清楚4中哪个为空。

像Nullable一样内置?

C#对Nullable<T>有一个很好的简写,您可以通过在类型后添加问号来使某些内容为空,例如int?

如果C#有类似于上面的NotNull<T>结构并有类似的速记,那就太好了,也许是感叹号(!),这样您就可以编写类似于:public void WriteName(Person! person)

当您尝试使用的类的对象未实例化时,会发生未设置为对象实例的空引用异常或对象引用。例如:

假设您有一个名为学生的类。

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

如上面的代码所示,语句学生的-只声明了学生类型的变量,注意此时没有实例化学生类。因此,当语句获取全名称被执行时,它将抛出NullRe的异常。

错误行“对象引用未设置为对象的实例”表示您没有将实例对象分配给对象引用,并且您仍然在访问该对象的属性/方法。

例如:假设您有一个名为myClass的类,它包含一个属性pro1

public Class myClass{public int prop1 {get;set;}}

现在你在其他类中访问这个prop 1,就像下面这样:

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

有趣的是,这个页面上的答案都没有提到两个边缘情况:

边缘情况#1:对字典的并发访问

. NET中的通用字典不是线程安全的,当您尝试从两个并发线程访问密钥时,它们有时可能会抛出NullReference甚至(更频繁)KeyNotFoundException。在这种情况下,例外是相当误导的。

边缘情况#2:不安全代码

如果unsafe代码抛出了NullReferenceException,你可能会查看你的指针变量,并检查它们是否有IntPtr.Zero或其他东西。这是一回事(“空指针异常”),但是在不安全的代码中,变量通常被转换为值类型/数组等,你会把头撞在墙上,想知道值类型如何抛出这个异常。

(顺便说一句,除非您需要,否则不使用不安全代码的另一个原因。)

边缘案例#3:Visual Studio多显示器设置,辅助显示器的DPI设置与主显示器不同

这种边缘情况是特定于软件的,属于Visual Studio 2019 IDE(可能还有更早的版本)。

重现该问题的方法:将工具箱中的任何组件拖到非主监视器上的Windows窗体上,其DPI设置与主监视器不同,您会看到一个弹出窗口,其中包含“对象引用未设置为对象实例”。根据这个线程,这个问题已经存在了相当长的一段时间,在撰写本文时,它仍然没有得到修复。

这基本上是一个Null引用异常.作为微软状态-

当您尝试访问值为null的类型的成员。

那是什么意思?

这意味着如果任何成员不持有任何值,并且我们正在让该成员执行某些任务,那么系统无疑会抛出一条消息并说-

“嘿,等等,那个成员没有值,所以它不能执行你交给它的任务。

异常本身表示正在引用某些东西但其值未设置。因此,这表示它仅在使用引用类型时发生,因为值类型不可为空。

如果我们使用值类型成员,则不会发生NullRe的异常。

class Program{static void Main(string[] args){string str = null;Console.WriteLine(str.Length);Console.ReadLine();}}

上面的代码显示了分配有null值的简单字符串。

现在,当我尝试打印字符串str的长度时,我确实得到了发生未处理的System. NullReReference ceException类型异常消息,因为成员str指向null,并且不能有任何null长度。

当我们忘记实例化引用类型时,也会出现'空引用异常'。

假设我有一个类和成员方法。我没有实例化我的类,只是命名了我的类。现在如果我尝试使用该方法,编译器会抛出错误或发出警告(取决于编译器)。

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未赋值,这意味着我们的变量有空值或没有。

为什么会发生?

  • 由于我们没有检查对象的值而导致出现NullReReference ceException。我们经常在代码开发中未检查对象值。

  • 当我们忘记实例化我们的对象时,它也会出现。使用可以返回或设置空值的方法、属性、集合等也可能是此异常的原因。

如何才能避免呢?

有各种方法和方法可以避免这种著名的例外:

  1. 显式检查:我们应该坚持检查对象、属性、方法、数组和集合是否为空的传统。这可以简单地使用条件语句来实现,例如if-撤出if-撤出等。

  2. 异常处理:管理此异常的重要方法之一。使用简单的try-catch-最终块,我们可以控制此异常并维护其日志。当您的应用程序处于生产阶段时,这非常有用。

  3. 空运算符:空合并运算符和空条件运算符也可以在为对象、变量、属性和字段设置值时派上用场。

  4. 调试器:对于开发人员来说,我们有调试的大武器。如果我们在开发过程中遇到NullRe的异常,我们可以使用调试器来找到异常的来源。

  5. 内置方法:GetValueOrDefault()、IsNullOrWhiteSpace()和IsNullor空()等系统方法检查空值,如果有空值,则分配默认值。

这里已经有很多很好的答案。你也可以在我的博客上查看更详细的描述和示例。

希望这也有帮助!

如果在保存或编译构建期间收到此消息,只需关闭所有文件,然后打开任何文件进行编译和保存。

对我来说,原因是我重命名了文件,旧文件仍然打开。

从字面上看,最简单的方法来修复NullReReference ceExeption有两种方法。

例如,如果您有一个带有附加脚本的GameObject和一个名为rb(刚体)的变量,则该变量将在您开始游戏时以null开头。这就是为什么你会得到一个NullReReference ceExeption,因为计算机没有存储在该变量中的数据。

我将使用RigidBody变量作为示例。我们可以通过以下几种方式轻松添加数据:

  1. 使用AddComponent>物理>刚体向对象添加刚体然后进入您的脚本并键入rb = GetComponent<Rigidbody>();这行代码在Start()Awake()函数下效果最好。
  2. 您可以以编程方式添加组件并同时使用一行代码分配变量:rb = AddComponent<RigidBody>();

进一步说明:如果你想Unity向你的对象添加一个组件,而你可能忘记添加一个,你可以在你的类声明上方键入[RequireComponent(typeof(RigidBody))](所有使用下面的空格)。

享受并享受制作游戏的乐趣!

您可以在C#6中使用空条件运算符以干净的方式修复NullReferenceException,并编写更少的代码来处理空检查。

它用于在执行成员访问(?.)或索引(?[)操作之前测试null。

示例

  var name = p?.Spouse?.FirstName;

它相当于:

    if (p != null){if (p.Spouse != null){name = p.Spouse.FirstName;}}

结果是当p为null或p.Spouse为null时,名称将为null。

否则,变量名将被分配为p.Spouse.FirstName的值。

对于更多详细信息:空条件运算符