Подтвердить что ты не робот

Зло использование Модификации Maybe и методов расширения в С#?

изменить 2015 Этот вопрос и его ответы больше не актуальны. Он был задан до появления С# 6, который имеет нулевой передатчик (?.), Который устраняет хакерские обходные пути, обсуждаемые в этом вопросе и последующих ответах. Начиная с 2015 года, в С# вы должны теперь использовать Form.ActiveForm?.ActiveControl?.Name.


Я думал о проблеме распространения NULL в .NET, что часто приводит к уродливому, повторяемому коду, подобному этому:

Попытка # 1 обычный код:

string activeControlName = null;
var activeForm = Form.ActiveForm;
if (activeForm != null)
{
    var activeControl = activeForm.ActiveControl;
    if(activeControl != null)
    {
        activeControlname = activeControl.Name;
    }
}

Было несколько обсуждений в StackOverflow о возможностях <T> monad, или используя какой-либо метод расширения "если не нуль":

Попытка # 2, метод расширения:

// Usage:
var activeControlName = Form.ActiveForm
                          .IfNotNull(form => form.ActiveControl)
                          .IfNotNull(control => control.Name);

// Definition:
public static TReturn IfNotNull<TReturn, T>(T instance, Func<T, TReturn> getter)
    where T : class
{
    if (instance != null ) return getter(instance);
    return null;
}

Я думаю, что это лучше, однако, есть немного синтаксической беспорядка с повторяющимися "IfNotNull" и лямбдами. Я сейчас рассматриваю этот дизайн:

Попытка # 3, Maybe <T> с расширением

// Usage:
var activeControlName = (from window in Form.ActiveForm.Maybe()
                         from control in window.ActiveControl.Maybe()
                         select control.Name).FirstOrDefault();

// Definition:
public struct Maybe<T> : IEnumerable<T>
      where T : class
{
    private readonly T instance;

    public Maybe(T instance)
    {
        this.instance = instance;
    }

    public T Value
    {
        get { return instance; }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return Enumerable.Repeat(instance, instance == null ? 0 : 1).GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

public static class MaybeExtensions
{
    public static Maybe<T> Maybe<T>(this T instance)
        where T : class
    {
        return new Maybe<T>(instance);
    }
}

Мой вопрос: это злое злоупотребление методами расширения? Это лучше, чем старые обычные нулевые проверки?

4b9b3361

Ответ 1

Интересно, что так много людей самостоятельно выбирают имя IfNotNull, для этого в С# - это должно быть самое разумное имя!:)

Самое раннее, что я нашел на SO: Возможные подводные камни использования этой (основанной на расширении) стенографии

Мой (в незнании выше): Труба вперед в С#

Еще один недавний пример: Как проверить нули в глубоком лямбда-выражении?

Существует несколько причин, по которым метод расширения IfNotNull может быть непопулярным.

  • Некоторые люди категорически заявляют, что метод расширения должен генерировать исключение, если его параметр this равен null. Я не согласен с тем, что имя метода дает понять.

  • Расширения, которые применяются слишком широко, будут иметь тенденцию загромождать меню автоматического завершения. Этого можно избежать путем правильного использования пространств имен, чтобы они не раздражали людей, которые их не хотят.

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

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

Ответ 2

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

Версия IfNotNull по крайней мере справляется с тем, чтобы избежать повторения, но я думаю, что она все еще слишком длинна, не будучи на самом деле более ясной.

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


Так же, как и в стороне, мой любимый метод расширения полу-зла:

public static void ThrowIfNull<T>(this T value, string name) where T : class
{
    if (value == null)
    {
        throw new ArgumentNullException(name);
    }
}

Это позволяет вам включить это:

void Foo(string x, string y)
{
    if (x == null)
    {
        throw new ArgumentNullException(nameof(x));
    }
    if (y == null)
    {
        throw new ArgumentNullException(nameof(y));
    }
    ...
}

в

void Foo(string x, string y)
{
    x.ThrowIfNull(nameof(x));
    y.ThrowIfNull(nameof(y));
    ...
}

Там все еще неприятное повторение имени параметра, но по крайней мере оно более аккуратное. Конечно, в .NET 4.0 я бы использовал Code Contracts, о чем я сейчас хочу писать... Qaru - отличное предотвращение работы;)

Ответ 3

Если вы хотите, чтобы метод расширения уменьшал вложенное, если у вас есть, вы можете попробовать что-то вроде этого:

public static object GetProperty(this object o, Type t, string p)
{
    if (o != null)
    {
        PropertyInfo pi = t.GetProperty(p);
        if (pi != null)
        {
            return pi.GetValue(o, null);
        }
        return null;
    }
    return null;
}

поэтому в коде, который вы только что сделали:

string activeControlName = (Form.ActiveForm as object)
    .GetProperty(typeof(Form),"ActiveControl")
    .GetProperty(typeof(Control),"Name");

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

(Примечание: я мог бы перепутать эти типы):)

Ответ 4

В случае, если вы имеете дело с С# 6.0/VS 2015 и выше, теперь у них есть встроенное решение для нулевого распространения:

string ans = nullableString?.Length.ToString(); // null if nullableString == null, otherwise the number of characters as a string.

Ответ 5

Исходный образец работает и проще всего прочитать с первого взгляда. Есть ли необходимость улучшить это?

Ответ 6

Решение IfNotNull является лучшим (пока команда С# не предоставит нам нуль-безопасный оператор разыменования, то есть).

Ответ 7

Я не слишком без ума от решения. Что не так с оригинальной версией оригинала:

string activeControlName = null;
if (Form.ActiveForm != null)
    if (Form.ActiveForm.ActivControl != null) activeControlname = activeControl.Name;

Если это не так, то я бы посмотрел на запись объекта NotNullChain или FluentNotNull, чем можно связать несколько не нулевых тестов в строке. Я согласен с тем, что метод расширения IfNotNull, действующий на нуль, кажется немного странным - хотя методы расширения - это просто синтаксический сахар.

Я думаю, что ответ Марка Синовека мог бы сделать общий.

ИМХО, я думаю, что основная команда С# должна смотреть на эту "проблему", хотя я думаю, что есть большие проблемы, которые нужно решать.

Ответ 8

Конечно, исходный 2-вложенный IF намного читабельнее других. Но предлагая вам более широко решить проблему, вот еще одно решение:

try
{
    var activeForm = Form.ActiveForm; assumeIsNotNull(activeForm);
    var activeControl = activeForm.ActiveControl; assumeIsNotNull(activeControl);
    var activeControlname = activeControl.Name;
}
catch (AssumptionChainFailed)
{
}

где

class AssumptionChainFailed : Exception { }
void assumeIsNotNull(object obj)
{
    if (obj == null) throw new AssumptionChainFailed();
}