如何在C#中创建Null对象

时间:2009-07-24 15:25:00

标签: c# design-patterns refactoring

Martin Fowler的Refactoring讨论了创建Null对象以避免大量的

if (myObject == null)

测试。 这样做的正确方法是什么?我的尝试违反了“构造函数中的虚拟成员调用”规则。 这是我的尝试:

public class Animal
{
    public virtual string Name { get; set; }
    public virtual string Species { get; set; }
    public virtual bool IsNull 
    { 
        get { return false; }
    }
}

public sealed class NullAnimal : Animal
{
    public override string Name
    {
        get{ return "NULL"; }
        set { }
    }
    public override string Species
    {
        get { return "NULL"; }
        set { }
    }
    public virtual bool IsNull
    {
        get { return true; }
    }
}

5 个答案:

答案 0 :(得分:24)

去查看有趣概念(例如DbNull)引起的痛苦程度,并考虑这是否真的是一个好主意。

Protip:如果你经常检查空引用,你可能应该重新考虑一下API,以帮助排除靠近堆栈顶部的空对象。

Protip II:当出现意外的null时抛出异常实际上很好并且花花公子。如果你的空位不应该为空,那么事情应该会好转。

答案 1 :(得分:12)

我倾向于同意Wyatt Barnett's answer,因为在创建这些“null”对象时你应该表现出克制。也就是说,有一些很好的理由这样做。有时候。

我也倾向于同意Supertux's answer,因为null对象的整个要点是不需要检查它是否为null,所以你应该丢失IsNull属性。如果您确实觉得需要IsNull属性,请再次阅读Wyatt的回复并重新考虑。

感谢CraigTP for the nice links了解更多信息。好东西。

现在我假设在你的真实代码中你实际上有一个构造函数试图设置Name或Species的值(无论你的实际代码等价物是什么)。否则,为什么你会得到“构造函数中的虚拟成员调用”警告/错误?当使用新奇的MyProperty时,我遇到了几个类似的问题{get;组;我自己的快捷方式(特别是在结构中使用时,并没有让我开始关于序列化版本控制)。你的解决方案是不使用快捷方式,而是采用传统方式。

public class Animal {
    protected Animal() { }

    public Animal(string name, string species) {
        _Name = name;
        _Species = species;
    }

    public virtual string Name {
        get { return _Name; }
        set { _Name = value; }
    }
    private string _Name;

    public virtual string Species {
        get { return _Species; }
        set { _Species = value; }
    }
    private string _Species;
}

public sealed class NullAnimal : Animal {
    public override string Name {
        get { return String.Empty; }
        set { }
    }
    public override string Species {
        get { return String.Empty; }
        set { }
    }
}

这解决了在构造函数中设置虚拟属性的问题。相反,您正在设置私有字段值(如果使用快捷方式,则无法引用该字段值)。要获得额外的功劳,请编译这两种方法,并使用Reflector查看生成的程序集。

我越用{get;组; }捷径,我越不喜欢它。

答案 2 :(得分:3)

Null对象模式的要点是它不需要空检查以防止崩溃或错误。

例如,如果您尝试对Species属性执行操作并且它为null - 则会导致错误。

所以,你不需要一个isNull方法,只需在getter中返回一些不会导致应用程序崩溃/出错的内容,例如:

public class Animal
{
    public virtual string Name { get; set; }
    public virtual string Species { get; set; }
}

public sealed class NullAnimal : Animal
{
    public override string Name
    {
        get{ return string.Empty; }
        set { ; }
    }
    public override string Species
    {
        get { return string.Empty; }
        set { ; }
    }
}

答案 3 :(得分:2)

如果合适,您只能使用此方法。您的Animal对象示例可能不是一个很好的示例,因为它没有提供您将使用此方法的适当情况。例如:

Animal animal = new Animal();

if (animal.tail == null)
{
    //do nothing because wagging a tail that doesn't exist may crash the program
}
else
{
    animal.wagTail();
}

在这个例子中,你应该构建Animal对象,这样如果动物没有尾巴,它就可以成功处理wagTail()命令而不会崩溃。

Class Animal
{
    Tail tail;

    void wagTail()
    {
        if (this.tail == null)
        {
            //do nothing
        }
        else
        {
            this.tail.doTheWag();
        }
    }
}

现在你不需要进行空检查,但是无论动物是否有尾巴,都可以调用animal.wagTail()。

答案 4 :(得分:0)

我想在这里提一些有趣的细节。看看你的班级。它有任何逻辑吗?从某种意义上说,这不是一个类,这是一个数据结构。您要做的是将null对象模式应用于不适用的对象模式。数据结构更接近于值类型,而不是类。因此可以使用null检查来解决您的问题。 空对象模式不是您应该始终遵循的。空对象模式是可以用来避免Liskov的替换原则违规的事情,用于表示不执行任何操作的类,因为null不适合替换类,因为它是值,而不是类。 但是价值类型和数据结构的不同之处。空是有价值的!所以在这种情况下,空检查是正确的。