Могу ли я вызвать собственное короткое замыкание в вызове метода? - PullRequest
5 голосов
/ 16 декабря 2009

Предположим, я хочу проверить несколько объектов, чтобы убедиться, что ни один из них не равен нулю:

if (obj != null &&
    obj.Parameters != null &&
    obj.Parameters.UserSettings != null) {

    // do something with obj.Parameters.UserSettings
}

Это заманчивая перспектива - написать вспомогательную функцию, которая будет принимать переменное число аргументов и упростит этот вид проверки:

static bool NoNulls(params object[] objects) {
    for (int i = 0; i < objects.Length; i++)
        if (objects[i] == null) return false;

    return true;
}

Тогда приведенный выше код может стать:

if (NoNulls(obj, obj.Parameters, obj.Parameters.UserSettings)) {
    // do something
}

Правильно? Неверно. Если obj равно нулю, тогда я получу NullReferenceException, когда попытаюсь передать obj.Parameters на NoNulls.

Таким образом, вышеуказанный подход явно ошибочен. Но оператор if, использующий оператор &&, прекрасно работает, поскольку он закорочен. Итак: есть ли способ сделать метод замкнутым, чтобы его аргументы не оценивались до тех пор, пока в методе не будут явно указаны ссылки?

Ответы [ 3 ]

9 голосов
/ 16 декабря 2009

Ну, это ужасно, но ...

static bool NoNulls(params Func<object>[] funcs) {
    for (int i = 0; i < funcs.Length; i++)
        if (funcs[i]() == null) return false;

    return true;
}

Затем назовите его с помощью:

if (NoNulls(() => obj,
            () => obj.Parameters,
            () => obj.Parameters.UserSettings)) {
    // do something
}

По сути, вы предоставляете делегатов для ленивой оценки значений, а не самих значений (поскольку оценка этих значений является причиной исключения).

Я не говорю, что это хорошо , но оно есть как вариант ...

РЕДАКТИРОВАТЬ: Это на самом деле (и случайно) доходит до сути того, что Дэн был после, я думаю. Все аргументы метода оцениваются перед выполнением самого метода. Эффективное использование делегатов позволяет отложить эту оценку до тех пор, пока метод не должен вызвать делегат для получения значения.

1 голос
/ 17 декабря 2009

Вы могли бы написать функцию, которая принимает дерево выражений и преобразует это дерево в форму, которая будет проверять наличие нулей, и возвращать Func<bool>, который можно было бы безопасно оценить, чтобы определить, существует ли нулевое значение.

Я подозреваю, что, хотя результирующий код может быть крутым, он будет сбивать с толку и будет намного менее производительным, чем просто написание нескольких коротких замыканий a != null && a.b != null.... На самом деле, это, вероятно, будет менее производительно, чем просто проверка всех значений и перехват NullReferenceException (не то, чтобы я выступал за обработку исключений в качестве механизма управления потоком).

Подпись для такой функции будет выглядеть примерно так:

public static Func<bool> NoNulls( Expression<Func<object>> expr )

и его использование будет выглядеть примерно так:

NoNulls( () => new { a = obj, 
                     b = obj.Parameters, 
                     c = obj.Parameters.UserSettings } )();

Если у меня будет немного свободного времени, я напишу функцию, которая выполняет именно такое преобразование дерева выражений, и обновлю свой пост. Однако я уверен, что Джон Скит или Марк Гравелл могли написать такую ​​функцию с одним закрытым глазом и одной рукой за спиной.

Мне также хотелось бы, чтобы в C # был реализован оператор .?, на который ссылается Эрик. Как сказал бы другой Эрик (Картман), это «надрал бы задницу».

0 голосов
/ 17 декабря 2009

Вы можете использовать отражения, если не возражаете против потери безопасности статического типа. Я возражаю, поэтому я просто использую вашу первую конструкцию с коротким замыканием. Хотелось бы упомянуть такую ​​функцию, как Эрик:)

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

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

Редактировать: (Ответы не позволяют мне вставлять блоки кода, поэтому редактирование моего сообщения)

Упс, не поспевал за этим. К сожалению об этом:)

Вы можете использовать отражения для поиска и оценки члена или свойства через строку. Класс, который написал один из моих друзей, имел следующий синтаксис:

new ReflectionHelper(obj)["Parameters"]["UserSettings"]

Это работало через цепочку методов, возвращая ReflectionHelper на каждом уровне. Я знаю, что NullReferenceException является проблемой в этом примере. Я просто хотел продемонстрировать, как оценка может быть отложена до времени выполнения.

Пример, чуть более близкий к полезности:

public class Something
{
  public static object ResultOrDefault(object baseObject, params string[] chainedFields)
  {
    // ...
  }
}

Опять же, этот синтаксис воняет. Но это демонстрирует использование строк + отражений для переноса оценки во время выполнения.

...