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

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

这意味着什么,我可以做什么来修复这个错误?


当前回答

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

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

其他回答

这意味着您的代码使用了一个设置为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
}

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

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

实例

  var name = p?.Spouse?.FirstName;

相当于:

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

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

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

有关详细信息:Null条件运算符

我有不同的观点来回答这个问题。这种回答是“我还能做什么来避免它?”

当跨不同层工作时,例如在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();
    }
}

你能怎么办?

这里有很多很好的答案来解释空引用是什么以及如何调试它。但是关于如何防止这个问题或者至少让它更容易被发现的问题却很少。

检查参数

例如,方法可以检查不同的参数以查看它们是否为空,并抛出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)的东西了。

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<>实例时使用的特殊规则。