我有一些代码,当它执行时,它会抛出NullReferenceException,说:
对象引用未设置为对象的实例。
这意味着什么,我可以做什么来修复这个错误?
我有一些代码,当它执行时,它会抛出NullReferenceException,说:
对象引用未设置为对象的实例。
这意味着什么,我可以做什么来修复这个错误?
当前回答
我有不同的观点来回答这个问题。这种回答是“我还能做什么来避免它?”
当跨不同层工作时,例如在MVC应用程序中,控制器需要服务来调用业务操作。在这种情况下,依赖注入容器可用于初始化服务以避免NullReferenceException。因此,这意味着您不必担心检查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
其中,从对象(或从System.ValueType或System.Enum类之一,或从接口类型)到值类型(非Nullable<>)的拆箱转换(强制转换)本身会产生NullReferenceException。
在另一个方向上,从HasValue等于false的Nullable<>到引用类型的装箱转换可能会给出一个空引用,然后会导致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<>实例时使用的特殊规则。
错误行“Object reference not set to an instance of a Object.”表示您尚未将实例对象分配给对象引用,但仍在访问该对象的财产/方法。
例如:假设您有一个名为myClass的类,它包含一个属性prop1。
public Class myClass
{
public int prop1 {get;set;}
}
现在,您正在访问其他类中的prop1,如下所示:
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;
}
}
你能怎么办?
这里有很多很好的答案来解释空引用是什么以及如何调试它。但是关于如何防止这个问题或者至少让它更容易被发现的问题却很少。
检查参数
例如,方法可以检查不同的参数以查看它们是否为空,并抛出ArgumentNullException,这显然是为此目的创建的异常。
ArgumentNullException的构造函数甚至将参数的名称和消息作为参数,以便您可以确切地告诉开发人员问题所在。
public void DoSomething(MyObject obj) {
if(obj == null)
{
throw new ArgumentNullException("obj", "Need a reference to obj.");
}
}
使用工具
还有几个库可以提供帮助。例如,“Resharper”可以在编写代码时向您提供警告,尤其是当您使用它们的属性:NotNullAttribute时
在“Microsoft代码契约”中,您可以使用Contract.Requals(obj!=null)这样的语法,这为您提供了运行时和编译检查:引入代码契约。
还有“PostSharp”,它允许您只使用如下属性:
public void DoSometing([NotNull] obj)
通过这样做并使PostSharp成为构建过程的一部分,将在运行时检查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和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引入了“空条件运算符”,这有点帮助。使用此功能,您可以引用嵌套对象,如果其中任何一个为空,则整个表达式返回空。
这减少了在某些情况下必须执行的空检查的数量。语法是在每个点前加一个问号。以以下代码为例:
var address = country?.State?.County?.City;
假设country是一个country类型的对象,该对象具有名为State等属性。如果country、State、County或City为空,则地址将变为空。因此,您只需检查地址是否正确。
这是一个很好的功能,但它提供的信息较少。这并不能明显看出4中的哪一个是空的。
像Nullable一样内置?
C#对Nullable<T>有一个很好的简写,你可以在类型后面加一个问号,比如so int?。
如果C#有类似于上面NotNull<T>结构的东西,并且有类似的速记,也许是感叹号(!),这样你就可以写类似于:public void WriteName(Person!Person)的东西了。
另一种可能发生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
在这里,Book和Car是不兼容的类型;汽车不能转换成书。当此强制转换失败时,as返回null。在此之后使用mybook会导致NullReferenceException。
通常,应使用强制转换或,如下所示:
如果您希望类型转换总是成功的(即,您知道对象应该是什么),那么应该使用强制转换:
ComicBook cb = (ComicBook)specificBook;
如果您不确定该类型,但希望尝试将其用作特定类型,请将其用作:
ComicBook cb = specificBook as ComicBook;
if (cb != null) {
// ...
}
有趣的是,本页的答案中没有一个提到两种边缘情况:
边缘案例#1:对字典的并发访问
.NET中的泛型字典不是线程安全的,当您尝试从两个并发线程访问密钥时,它们有时可能会抛出NullReference,甚至(更频繁)抛出KeyNotFoundException。在这种情况下,这个例外情况很容易引起误解。
边缘案例#2:不安全代码
如果不安全代码引发NullReferenceException,您可以查看指针变量,并检查它们是否存在IntPtr.Zero或其他内容。这是同一回事(“空指针异常”),但在不安全的代码中,变量通常被转换为值类型/数组等,你会把头撞在墙上,想知道值类型如何抛出此异常。
(顺便说一句,除非您需要,否则不使用不安全代码的另一个原因。)
边缘案例3:Visual Studio多监视器设置,辅助监视器的DPI设置与主监视器不同
此边缘案例是特定于软件的,属于Visual Studio 2019 IDE(以及可能更早的版本)。
一种重现问题的方法:将任何组件从工具箱拖到非主监视器上的Windows窗体上,该窗体的DPI设置与主监视器不同,然后会弹出一个“Object reference not set to A instance of A Object”(对象引用未设置为对象的实例)。根据这个线程,这个问题已经知道了很长一段时间,在编写时仍然没有解决。