Как стать автором
Обновить

3 способа использовать оператор?.. неправильно в C# 6

Время на прочтение4 мин
Количество просмотров26K
Наверняка вы уже знаете об операторе безопасной навигации ("?." операторе) в C# 6. В то время как это довольно хороший синтаксический сахар, я хотел бы отметить варианты злоупотребления этим оператором.

Вариант 1


Я помню как я читал на одном из форумов как кто-то высказывался, что команда, разрабатывающая C#, должна сделать поведение при "?." (автоматическую проверку на null) поведением по умолчанию при навигации внутрь объектов вместо того, что сейчас происходит при ".". Давайте порассуждаем немного об этом. Действительно, почему бы и не изменить имплементацию для "."? Другими словами, почему не использовать "?." оператор везде, просто на всякий случай?

Одно из основных назначений вашего кода — раскрывать ваши намерения другим разработчикам. Повсеместное использование оператора "?." вместо "." приводит к ситуации, в которой разработчики не способны понять, действительно ли код ожидает null или кто-то поместил эту проверку просто потому что это было легко сделать.

public void DoSomething(Order order)
{
    string customerName = order?.Customer?.Name;
}

Действительно ли этот метод ожидает, что параметр order может быть null? Может ли свойство Customer также возвращать null? Этот код сообщает, что да: и параметр order, и свойство Customer могут обращаться в null. Но это перестает быть таковым если автор поместил эти проверки просто так, без понимания того, что он делает.

Использование оператора "?." в коде без действительной необходимости в нем приводит к путанице и ухудшает читаемость кода.

Эта проблема тесно связана с отсутствием ненулевых ссылочных типов в C#. Было бы гораздо проще читать код если они были бы добавлены на уровне языка. К тому же, подобный код приводил бы к ошибке компиляции:

public void DoSomething(Order! order)
{
    Customer customer = order?.Customer; // Compiler error: order can’t be null
}

Вариант 2


Еще один способ злоупотребления оператором "?." — полагаться на null там, где это не требуется. Рассмотрим пример:

List<string> list = null;
int count;
if (list != null)
{
    count = list.Count;
}
else
{
    count = 0;
}

Этот код имеет очевидный «запах». Вместо использования null, в данном случае лучше использовать паттерн Null object:

// Пустой список - пример использования паттерна Null object
List<string> list = new List<string>();
int count = list.Count;

Теперь же с новым "?." оператором, «запах» уже не так очевиден:

List<string> list = null;
int count = list?.Count ?? 0;

В большинстве случаев, паттерн Null object — намного более предпочтительный выбор чем null. Он не только позволяет устранить проверки на null, но также помогает лучше выразить доменную модель в коде. Использование оператора "?." может помочь с устранением проверок на null, но никогда не сможет заменить необходимость построения качественной модели предметной области в коде.

С новым оператором очень легко писать подобный код:

public void Process()
{
    int result = DoProcess(new Order(), null);
}
 
private int DoProcess(Order order, Processor processor)
{
    return processor?.Process(order) ?? 0;
}

В то время как было бы правильней выразить эту логику с использованием Null object:

public void Process()
{
    var processor = new EmptyProcessor();
    int result = DoProcess(new Order(), processor);
}
 
private int DoProcess(Order order, Processor processor)
{
    return processor.Process(order);
}

Вариант 3


Часто подобный код показывается как хороший пример применения нового оператора:

public void DoSomething(Customer customer)
{
    string address = customer?.Employees
        ?.SingleOrDefault(x => x.IsAdmin)?.Address?.ToString();
    SendPackage(address);
}

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

Код выше нарушает принципы инкапсуляции. С точки зрения доменной модели, намного более правильным было бы добавить отдельный метод:

public void DoSomething(Customer customer)
{
    Contract.Requires(customer != null);
 
    string address = customer.GetAdminAddress();
    SendPackage(address);
}

Сохраняя таким образом инкапсуляцию и устраняя необходимость в проверках на null. Использование оператора "?." может замаскировать проблемы с инкапсуляцией. Лучше не поддаваться искушению использовать оператор "?." в подобных случаях, даже если «сцеплять» вызовы методов друг за другом может казаться очень простой задачей.

Валидные примеры использования


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

Другой хороший пример — вызов ивента:

protected void OnNameChanged(string name)
{
    NameChanged?.Invoke(name);
}

Остальные примеры сводятся к тем, которые не подпадают под три варианта невалидного использования, описанных выше.

Заключение


В то время как оператор "?." может помочь уменьшить количество кода в некоторых случаях, он также может замаскировать признаки плохого дизайна (design smells), которые могли бы быть более очевидными без него.

Для того чтобы решить, нужно или нет использовать этот оператор в каком-то конкретном случае, просто подумайте будет ли код, написанный «по старинке» допустимым. Если да, то смело используйте этот оператор. Иначе, постарайтесь отрефакторить код и убрать недостатки в дизайне.

Английская версия статьи: 3 misuses of "?." operator in C# 6
Теги:
Хабы:
Всего голосов 20: ↑17 и ↓3+14
Комментарии80

Публикации

Истории

Работа

Ближайшие события