Как создать нулевой объект в C#

рефакторинг Мартина Фаулера обсуждает создание нулевых объектов, чтобы избежать множества

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 ответов


Я склонен согласиться с Wyatt Barnett's answer в этом вы должны проявлять сдержанность при создании этих видов" нулевых " объектов. Тем не менее, есть несколько хороших причин для этого. Иногда.

Я также склонен согласиться с Supertux это в том, что весь смысл нулевого объекта заключается в том, чтобы не проверять, является ли он нулевым, поэтому вы должны потерять свойство IsNull. Если вы действительно чувствуете, что вам нужно свойство IsNull, то прочитайте Wyatt's ответьте еще раз и передумайте.

и спасибо CraigTP для хороших ссылок для получения дополнительной информации. Хороший материал.

теперь я предположу, что в вашем реальном коде у вас действительно есть конструктор, который пытается установить значения Name или Species (независимо от того, как может называться ваш реальный эквивалент кода). В противном случае, почему вы получите предупреждение/ошибку "вызов виртуального члена в конструкторе"? Я столкнулся с парой подобных проблем при использовании newfangled Myproperty { get; set; } сам ярлык (особенно при использовании в структурах, и не заставляйте меня начинать о сериализации версий). Ваше решение состоит в том, чтобы не использовать ярлык, а вместо этого делать это старомодным способом.

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

это решает проблему установки виртуальных свойств в конструкторе. Вместо этого вы устанавливаете значения частного поля (что-то, на что у вас нет возможности ссылаться, если вы используете ярлык). Для дополнительного кредита скомпилируйте оба метода и используйте отражатель для просмотра результирующих сборок.

чем больше я использую ярлык { get; set;}, тем больше он мне не нравится.


посмотрите, сколько боли вызвали интересные концепции, такие как DbNull, и подумайте, действительно ли это хорошая идея.

Protip: если вы постоянно проверяете нулевые ссылки, вам, вероятно, следует немного переосмыслить API, чтобы помочь исключить нулевые объекты ближе к верхней части стека.

Protip II: имея что-то бросить исключение, когда есть неожиданный null на самом деле хорошо и денди. Вещи должны идти бум, если у вас есть nulls где не должно быть null.


точка шаблона нулевого объекта заключается в том, что для предотвращения сбоя или ошибки не требуется проверка null.

например, если вы попытаетесь выполнить операцию над свойством Species и она будет равна null-это вызовет ошибку.

Итак, вам не нужен метод isNull, просто верните что-то в геттере, что не приведет к сбою/ошибке приложения, например:

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

теперь вам не нужно делать проверку на null, но можно просто позвонить животное.трясогузка() независимо от того, имеет ли животное хвост или нет.


Я хотел бы упомянуть здесь некоторые интересные детали. Посмотри на свой класс. В этом есть какая-то логика? Это не класс в его понимании это структура данных. То, что вы пытаетесь сделать, это применить шаблон нулевого объекта к чему-то, к чему он неприменим. Структуры данных ближе к типам значений, чем к классам. Там fore null check может быть на месте, чтобы решить вашу проблему. Шаблон нулевого объекта-это не то, чему вы всегда должны следовать. Шаблон Null object-это то, что вы можете использовать для избегайте нарушения принципа подстановки Лискова, чтобы представить класс, который ничего не делает, потому что null не является подходящей подстановкой для класса, поскольку это значение, но не класс. Но с типами значений и структурами данных все по-другому. Null-это значение! Поэтому в этом случае проверка null-это правильная вещь.