我有一些代码,当它执行时,它会抛出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条件运算符

其他回答

另一种可能发生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) {
   // ...
}

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

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

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

其中一个示例是带有Calendar控件的简单ASP.NET绑定片段:

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

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

TL;DR:尝试使用Html.Parate而不是Renderpage


当我试图通过发送模型在视图中呈现视图时,我得到的对象引用未设置为对象的实例,如下所示:

@{
    MyEntity M = new MyEntity();
}
@RenderPage("_MyOtherView.cshtml", M); // error in _MyOtherView, the Model was Null

调试显示模型在MyOtherView中为Null。直到我将其更改为:

@{
    MyEntity M = new MyEntity();
}
@Html.Partial("_MyOtherView.cshtml", M);

它奏效了。

此外,我没有Html.Paraal的原因是,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误读了符号和括号。

你能怎么办?

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

检查参数

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

更新C#8.02019:可为空的引用类型

C#8.0引入了可为null的引用类型和不可为null引用类型。因此,必须只检查可为null的引用类型,以避免出现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。尽管在处理引用类型时需要保持警惕!

正如名称所暗示的那样,只有引用类型可以保存引用或直接指向任何对象(或“null”)。而值类型始终包含值。

引用类型(必须选中这些类型):

动态对象一串

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

数字类型整数类型浮点类型十进制的布尔用户定义的结构