我有一些代码,当它执行时,抛出了一个 NullReferenceException
异常,显示:
对象引用未设置为对象的实例。
这是什么意思?我该如何修复此错误?
我有一些代码,当它执行时,抛出了一个 NullReferenceException
异常,显示:
对象引用未设置为对象的实例。
这是什么意思?我该如何修复此错误?
Debugger.Break
可能会很有用,它会提示启动调试器。myString
评估为null。这可以通过查看Watch Window或在Immediate Window中运行表达式来验证。var x = myString.Trim();
str1
是否为空或str2
是否为空。var x = str1.Trim() + str2.Trim();
1 如果“在抛出异常时中断”太过严格,调试器会在.NET或第三方库中的NPE上停止,可以使用Break on User-Unhandled来限制捕获的异常。另外,VS2012引入了Just My Code,我建议也启用。
如果启用了仅调试我的代码,行为会略有不同。启用了仅调试我的代码后,调试器会忽略在我的代码之外抛出并且没有通过我的代码传递的CLR常见运行时异常的first-chance异常。
object o = null;
DateTime d = (DateTime)o; // NullReferenceException
当从object
(或者来自于类System.ValueType
,System.Enum
或接口类型之一)转换为除了Nullable<>
以外的值类型时,进行拆箱转换(强制类型转换)会导致NullReferenceException
异常。
反过来,从一个HasValue
等于false
的Nullable<>
进行装箱转换到引用类型会得到一个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<>
实例时使用的特殊规则引起的。
当实体框架中的实体类名称与Web表单代码后台文件的类名称相同时,需要添加一个特殊情况。
假设您有一个名为Contact.aspx的Web表单,其代码后台类为Contact,而您的实体名称为Contact。
当调用context.SaveChanges()时,以下代码将抛出NullReferenceException。
Contact contact = new Contact { Name = "Abhinav"};
var context = new DataContext();
context.Contacts.Add(contact);
context.SaveChanges(); // NullReferenceException at this line
为了完整起见,DataContext类
public class DataContext : DbContext
{
public DbSet<Contact> Contacts {get; set;}
}
以及联系实体类。有时实体类是部分类,这样您就可以在其他文件中扩展它们。
public partial class Contact
{
public string Name {get; set;}
}
当实体类和后台代码类在同一个命名空间中时,会出现此错误。为了解决这个问题,请将实体类或 Contact.aspx 的后台代码类重命名。
原因我仍然不确定原因。但是,每当任何实体类扩展 System.Web.UI.Page 时,都会发生此错误。
另一种可能会出现此异常的通用情况涉及单元测试期间模拟类。无论使用哪种模拟框架,您都必须确保正确模拟了类层次结构的所有适当级别。特别是,测试代码引用的HttpContext
的所有属性都必须被模拟。
请参见"在测试自定义AuthorizationAttribute时引发NullReferenceException",其中有一个相对冗长的例子。
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();
}
}
在“我该怎么做”这个问题上,可能会有很多答案。
防止开发过程中出现此类错误条件的一种更“正式”的方法是在代码中应用契约式设计。这意味着您需要在开发过程中设置类不变量,并且甚至可以在系统中设置函数/方法前置条件和后置条件。
简而言之,类不变量确保在正常使用时将存在一些约束条件,不会违反这些条件(因此,类将不会进入不一致状态)。前置条件意味着作为函数/方法输入的数据必须遵循一些设定的约束条件,并且永远不会违反它们,而后置条件意味着函数/方法输出必须再次遵循设置的约束条件,而且永远不会违反它们。 在没有漏洞的程序执行期间,契约条件绝不能被违反,因此在调试模式下检查契约式设计,同时在发布时禁用,以最大化开发系统性能。
这样,您就可以避免由于违反设定约束条件而导致的NullReferenceException
情况。例如,如果您在类中使用对象属性X
,然后尝试调用其方法并且X
具有空值,那么这将导致NullReferenceException
:
public X { get; set; }
public void InvokeX()
{
X.DoSomething(); // if X value is null, you will get a NullReferenceException
}
但如果您将“属性X永远不应该具有空值”设置为方法前置条件,那么您可以防止上述情况发生:
//Using code contracts:
[ContractInvariantMethod]
protected void ObjectInvariant()
{
Contract.Invariant(X != null);
//...
}
因此,Code Contracts 项目适用于 .NET 应用程序。
或者,可以使用 assertions 来应用按合同设计。
更新:值得一提的是,该术语是由 Bertrand Meyer 在他设计 Eiffel 编程语言时创造的。
当我们试图访问空对象的属性或者字符串值变为空并且我们试图访问字符串方法时,会抛出一个NullReferenceException
。
例如:
当访问一个空字符串的方法时:
string str = string.Empty;
str.ToLower(); // throw null reference exception
当访问 null 对象的属性时:
Public Class Person {
public string Name { get; set; }
}
Person objPerson;
objPerson.Name /// throw Null refernce Exception
String.Empty.ToLower()
不会引发空引用异常。它代表一个实际的字符串,尽管是一个空字符串(即 ""
)。由于这里有一个对象可调用 ToLower()
方法,因此在那里抛出空引用异常是没有意义的。 - KjartanTL;DR:尝试使用Html.Partial
而不是Renderpage
。
当我尝试通过传递模型来在一个视图中呈现另一个视图时,出现了Object reference not set to an instance of an object
的错误:
@{
MyEntity M = new MyEntity();
}
@RenderPage("_MyOtherView.cshtml", M); // error in _MyOtherView, the Model was Null
调试显示MyOtherView内的模型为空。直到我将其更改为:
@{
MyEntity M = new MyEntity();
}
@Html.Partial("_MyOtherView.cshtml", M);
而且它起作用了。
此外,我一开始没有使用Html.Partial
是因为Visual Studio有时会在不同构造的foreach
循环内部下方波浪线看起来像错误,即使它不是真的错误:
@inherits System.Web.Mvc.WebViewPage
@{
ViewBag.Title = "Entity Index";
List<MyEntity> MyEntities = new List<MyEntity>();
MyEntities.Add(new MyEntity());
MyEntities.Add(new MyEntity());
MyEntities.Add(new MyEntity());
}
<div>
@{
foreach(var M in MyEntities)
{
// Squiggly lines below. Hovering says: cannot convert method group 'partial' to non-delegate type Object, did you intend to envoke the Method?
@Html.Partial("MyOtherView.cshtml");
}
}
</div>
但我能够在这个“错误”的情况下无问题地运行应用程序。通过将foreach
循环的结构更改为以下方式,我成功摆脱了这个错误:
@foreach(var M in MyEntities){
...
}
尽管我有一种感觉,那就是因为Visual Studio误读了&和括号。
Html.Partial
,而不是 @Html.Partial
。 - John Saunders你能做些什么?
这里有很多好的答案,解释了空引用是什么以及如何调试它。但是在如何预防此问题或至少使其更容易捕获方面却很少有涉及。
检查参数
例如,方法可以检查不同的参数是否为null并抛出一个ArgumentNullException
,这是显然为此目的而创建的异常。
ArgumentNullException
的构造函数甚至接受参数的名称和消息,因此您可以准确地告诉开发人员问题所在。
public void DoSomething(MyObject obj) {
if(obj == null)
{
throw new ArgumentNullException("obj", "Need a reference to obj.");
}
}
使用工具
还有一些库可以帮助你。例如,“Resharper”可以在你编写代码时提供警告,特别是如果你使用他们的属性:“NotNullAttribute”。
有“Microsoft Code Contracts”,在那里你使用类似于 Contract.Requires(obj != null)
的语法,这将为你提供运行时和编译检查:介绍代码契约。
还有“PostSharp”,只需像这样使用属性:
public void DoSometing([NotNull] obj)
obj
将在运行时检查是否为空。请参见: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 exception
NotNull<Person> person = new Person(); // OK
NotNull<Person> person = GetPerson(); // throws exception if GetPerson() returns null
NotNull<T>
可以隐式地转换为T
,因此您几乎可以在任何需要它的地方使用它。例如,您可以将一个Person
对象传递给一个接受NotNull<Person>
的方法:
Person person = new Person { Name = "John" };
WriteName(person);
public static void WriteName(NotNull<Person> person)
{
Console.WriteLine(person.Value.Name);
}
如上所述,与可空类型一样,您可以通过 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-conditional operator”这一特性,有助于解决这个问题。使用这个特性,您可以引用嵌套对象,如果其中任何一个对象是null
,整个表达式就返回null
。
有时候,这可以减少您需要进行的空值检查的数量。语法是在每个点之前加上一个问号。例如,看下面的代码:
var address = country?.State?.County?.City;
country
是类型为Country
的对象,它有一个名为State
的属性等等。如果country
、State
、County
或City
为空,则address will be
null。因此,您只需要检查
address是否为空。
这是一个很棒的功能,但它提供的信息较少。它并没有明确告诉你这4个中哪一个是null。
类似于Nullable的内置结构?
C#中有一个很好的简写方式Nullable<T>
,您可以通过在类型后面加上问号来使其可空,如int?
。
如果C#有像上面的NotNull<T>
结构,并且有类似的简写方式,比如感叹号(!),那就太好了,这样您就可以编写类似于public void WriteName(Person! person)
的代码了。
使用 C# 6 中的 Null-conditional 运算符可以更加简洁地处理 NullReferenceException
,并且写更少的代码来处理 null 检查。
它用于在执行成员访问 (?.) 或索引 (?[) 操作之前测试是否为 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
的值。
更多详情请参见:Null-conditional Operators