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

4
假设我有三个类A、B和C,它们是嵌套的,使得我可以通过A.B.C访问C。如果我试图访问C的一个简单属性,比如Id类型为int,并且在A中没有实例化一个新的B(或在B中没有实例化C),我会得到一个“对象引用未设定为对象的实例”的错误。
我想知道的是,是否有一种简单的方法来获取A.B.C.Id,如果由于某些原因沿途有任何一个值为空,就给我一个默认值。
var myId = A.B.C.Id <some operator> -1;

目前我正在做类似这样的事情,但感觉很糟糕。

var myId = A == null ? -1 : A.B == null ? -1 : A.B.C == null ? -1 : A.B.C.Id;
7个回答

4
通过A或B或两者都经过C引用C,违反了德米特法则,这是不好的实践。如果需要以这种方式引用某些内容,请使用空构造函数来建立连接。

根据您对Demeter法则的严格解释而定。person.Name.FirstNameperson.Name.LastName是否违反了该法律?我认为大多数人不会那么严格。 - Ruben
我认为他指的是逻辑类。我猜包含方法的任何东西都可以。我不知道是否有一个书面规定+1; - Damien
@Ruben 狄米特法则有时候可以被打破,但如果你违反了这个原则,我建议你审视一下你的结构,看看是否可以构造你的对象,以避免违反这个原则。因为你违反狄米特法则的次数越少,你的代码越容易测试,因为你可以更加清晰地注入依赖关系。 - AutomatedTester
这并不是关于违反法律的问题。点的数量并不是衡量你是否违反Demeter法则的严格标准。有时一个“对象”由几个逻辑部分组成,你可以将它们作为子属性分组,仅仅是为了提供更好的对象逻辑设计。即使你的点数大于1,你也没有违反法则本身。嗯,这取决于你的解释。 - Ruben
有趣。以前没有读过这个,但可以理解规则及其存在的原因,然而重构不是一个选项,正如Damien的回答中提到的,在我的情况下,null对象属性是有效的,因为它的空值正在驱动其他逻辑。 - NJE
@Ruben,你也遇到了我所处的情况,这些子对象实际上不过是一个非常大的对象的逻辑部分。 - NJE

2
在C#中没有特殊的语法可以做到你想要做的事情。但是有一些架构上的方法可以实现。
你可以使用Null Object设计模式,或者你可以使用扩展方法扩展A、B、C类,使其支持null。
var myId = A.BOrNull().COrNull().IdOrNull();

public class A {
    public B B { get; set; }
}
public static class AExtensionMethods {
    public static B BOrNull( this A a ) {
        return null != a ? a.B : null;
    }
}

public class B {
    public C C { get; set; }
}
public static class BExtensionMethods {
    public static C COrNull( this B b ) {
        return null != b ? b.C : null;
    }
}

public class C {
    public int Id { get; set; }
}
public static class CExtensionMethods {
    public static int? IdOrNull( this C c ) {
        return null != c ? (int?)c.Id : null;
    }
}

我接受这个回答,因为TcKs付出了最大的努力来解决我的问题。我很感激,在理想的情况下,我会重新设计并摆脱这个困境,但不幸的是我在那里不工作 :)。 - NJE

1

这对于Null Object设计模式来说是一个很好的匹配。

基本上,像这样定义NullB:

public class NullB : B
{
    public override C C
    {
        get { return new NullC(); }
    }
}

然后这样定义NullC:

public class NullC : C
{
    public override int Id
    {
        get { return 0; }
    }
}

这显然需要相关类具有虚成员,但这样更安全。然后,当你拥有真实的B和C实例时,只需使用并返回它们即可。

1

看一下依赖注入,当你创建A的新实例时,它将在B中创建C,在A中创建B。或者只需将创建这些实例的代码放在类的构造函数中。(因此在A()中创建B和在B()中创建C)

之所以感觉不对,是因为正如其他人指出的那样,你不应该处于可以发生这种情况的情况下。


1
它不能解决ID为空的问题,但它将解决依赖类(在本例中为B和C)为空的问题,因为它们将随A一起创建。 - Damien
A 依赖于 B,因此当 A 被创建时,B 会自动创建。B 依赖于 C,因此当 B 被创建时,C 也会被创建。 - Damien
我理解这个问题是“B或C可能为空,而且这是有效的状态”。这就是我提出这个问题的原因。 - TcKs
@TcKs 你说得对,我之所以提出这个问题是因为子对象状态为空是有效的。如果为空意味着“不存在”,那么依赖注入就不会对我起作用。 - NJE
好的,它处于这种状态有点奇怪。因此我自己也感到困惑 :) - Damien

1

如果你必须深入其中,你可能想在A中创建一个属性,该属性返回null或B.C.Id。


我会建议深入处理: A.I获取{return B == null ? -1 : B.I} B.I获取{return C == null ? -1 : C.I} 但如果您有很多属性,则这不是一个选项。 - Toto

0

您可以使用属性来检查请求的类是否为null。如果是,它将创建一个新的类实例。

class ClassA
{
    private ClassB m_ClassB = null;
    public ClassB B
    {
        get
        {
            //check if we already have an instance of the class.
            //if not create it
            if (m_ClassB == null)
            {
                m_ClassB = new ClassB();
            }
            return m_ClassB;
        }
    }
}

class ClassB
{
    private ClassC m_ClassC = null;
    public ClassC C
    {
        get
        {
            //check if we already have an instance of the class.
            //if not create it
            if (m_ClassC == null)
            {
                m_ClassC = new ClassC();
            }
            return m_ClassC;
        }
    }
}

class ClassC
{
    private int m_Id = 123;
    public int ID
    {
        get
        {
            return m_Id;
        }
    }
}

然后你可以像这样访问你需要的内容

 CLassA A = new ClassA();
 int id = A.B.C.ID;

-2
你可以尝试这段代码:
var myId = -1;
try
{
  myId = A.B.C.Id;
}
catch (NullReferenceException e)
{
  myId = -1;
}

2
-1 这是非常糟糕的方式,如何处理呢?如果经常抛出异常,会使程序变慢,也会增加调试的难度。 - TcKs
3
在程序中使用异常来控制流程是一种不良的编程实践。在这种情况下,您可以首先轻松地检查 null,以避免抛出异常。 - Matt Howells
2
不要将异常用作控制流。如果您明确检查 null,您的代码将更易读和可维护。 - Curt Nichols
2
使用异常来控制程序流程是非常糟糕的做法。如果需要检查空值或创建一个不存在的类实例,最好采用更好的方法。 - Luiz Damim
不应该使用catch块来处理已经预期的异常。将其设置为-1意味着您知道可以预期什么。在这种情况下,检查空异常并在该条件下将myId设置为-1。 - Jonathan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接