Чем это лучше, чем оператор Null Coalesce? - PullRequest
2 голосов
/ 28 августа 2011

Я натолкнулся на это сообщение в блоге сегодня.

Я подведу итоги. Блоггер комментирует этот код и говорит, что он уродлив.

// var line1 = person.Address.Lines.Line1 ?? string.Empty;
// throws NullReferenceException: 
//    {"Object reference not set to an instance of an object."}

// The ugly alternative

var line1 = person.Address == null
    ? "n/a"
    : person.Address.Lines == null
    ? "n/a"
    : person.Address.Lines.Line1;

Затем блоггер пишет класс, который позволяет заменить приведенный выше код новым синтаксисом.

var line2 = Dis.OrDat<string>(() => person.Address.Lines.Line2, "n/a");

Код для класса Dis выглядит следующим образом.

 public static class Dis
  {
    public static T OrDat<T>(Expression<Func<T>> expr, T dat)
    {
      try
      {
        var func = expr.Compile();
        var result = func.Invoke();
        return result ?? dat; //now we can coalesce
      }
      catch (NullReferenceException)
      {
        return dat;
      }
    }
  }

Итак, первый вопрос, который у меня возникает, - почему исходный код, содержащий ??, должен быть заменен на проклятый код ?:.

Мой второй вопрос: зачем использовать дерево выражений над Null Coalesce? Единственная причина, по которой я могу думать об этом, заключается в том, что они используют ленивую инициализацию, но это не ясно из поста и не ясно из тестового кода, который находится в посте блога. Я также опубликую это здесь.

Кстати, кто-нибудь знает, как создать окно кодового блока фиксированного размера? Или здесь предпочтительней кодовый блок без прокрутки? Я ничего не видел в Мета блоге.

Ответы [ 6 ]

6 голосов
/ 28 августа 2011

Я не вижу смысла использовать деревья выражений в блоговом примере, там достаточно Func.

Мое предложение - использовать опцию Maybe * monad вместо этого кода. Смотрите пример.

public static class Maybe
{
    public static TResult With<T, TResult>(this T self, Func<T, TResult> func) where T : class
    {
        if (self != null)
            return func(self);
        return default(TResult);
    }

    public static TResult Return<T, TResult>(this T self, Func<T, TResult> func, TResult result) where T : class
    {
        if (self != null)
            return func(self);
        return result;
    }
}

И твой код становится:

var line2 = person
   .With(p => p.Address)
   .With(a => a.Lines)
   .Return(l => l.Line2, "n/a");

[*] Это не настоящая монада, а очень упрощенная версия.

4 голосов
/ 28 августа 2011

код блога хуже. Имя метода Dis.OrDat безобразно и не описывает, что на самом деле делает метод.

Использование Expression<T> является избыточным, оно может быть просто:

public static T OrDat<T>(Func<T> func, T dat)
{
  try
  {
    return func() ?? dat;
  }
  catch (NullReferenceException)
  {
    return dat;
  }
}

Он сразу звонит Compile и Invoke один за другим, поэтому он ничего не делает с деревом выражений. Передача Func<T> в том же виде будет такой же, без затрат на компиляцию Func.

Но что еще хуже, в коде используются исключения для управления потоком , что всегда плохо: свойство person.Address представляется необязательным, поэтому оно не является "исключительным" для того, чтобы быть нулевым, поэтому исключение не должно быть вызвано кодом, который его использует. Приведенный выше catch не может различить person.Address == null и реализацию метода получения свойства Address, который внутренне нарушается, вызывая выброс NullReferenceException. Он просто проглатывает их всех.

Итак, в целом, я был бы рад игнорировать сообщение в блоге.

3 голосов
/ 28 августа 2011

Для защиты этого кода:

var line1 = person.Address.Lines.Line1 ?? string.Empty;

от выбрасывания NullReferenceException

Я бы просто использовал:

var line1 = string.Empty;
if ((person.Address != null) && (person.Address.Lines != null))
   line1 = person.Address.Lines.Line1 ?? string.Empty;

а не Блоггеры того или иного (Dis.OrDat),

2 голосов
/ 13 января 2017

Для тех, кто просматривает эту запись, но использует C # 6.0 (или выше), код теперь может использовать Нулевое распространение и записываться следующим образом:

var line1 = person?.Address?.Lines?.Line1 ?? "n/a";

2 голосов
/ 28 августа 2011

Для вашего первого вопроса, код вопроса:

var line1 = person.Address.Lines.Line1 ?? string.Empty;

выдаст NullReferenceException, если person, Address или Lines равно null. Код замены, использующий троичные операторы if, защищает от этого случая. Нулевой оператор объединения будет работать только со свойством Line1, поэтому он не может защитить от остальной части выражения null.

Что касается вашего второго вопроса, то причина использования дерева выражений, вероятно, заключается в том, чтобы «упростить» код, необходимый для обеспечения возможности полного вычисления всего выражения. Хотя код будет работать, я думаю, что он вводит уровень сложности и накладных расходов, который не является действительно необходимым или необходимым.

2 голосов
/ 28 августа 2011

проблема с первой строкой кода (var line1 = person.Address.Lines.Line1 ?? string.Empty) состоит в том, что она выдаст ошибку, если person, Address или Lines равен нулю. Нулевой оператор объединения работает только с результатом всего выражения.

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

...