如何在C#中创建Null对象

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; } } } 

我倾向于同意Wyatt Barnett的答案 ,因为在创建这些“空”对象时你应该表现出克制。 也就是说,有一些很好的理由这样做。 不定期的。

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

感谢CraigTP提供更多信息的好链接 。 好东西。

现在我将假设在您的实际代码中,您实际上有一个构造函数,它试图设置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; 组; }快捷方式,我越不喜欢它。

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

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

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

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 { ; } } } 

如果合适,您只能使用此方法。 您的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()。

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