Вызов метода, если не null в С#

Можно ли как-то сократить это утверждение?

if (obj != null)
    obj.SomeMethod();

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

И аналогичная проблема с событиями, где

public event Func<string> MyEvent;

а затем вызовите

if (MyEvent != null)
    MyEvent.Invoke();

Ответ 1

Начиная с С# 6, вы можете просто использовать:

MyEvent?.Invoke();

или

obj?.SomeMethod();

?. является оператором, несущим нуль, и вызывает замыкание .Invoke(), когда операнд null. Операнд доступен только один раз, поэтому нет риска "изменения значений между проверкой и вызовом".

===

До С# 6 нет: нет никакой нулевой магии, за одним исключением; методы расширения - например:

public static void SafeInvoke(this Action action) {
    if(action != null) action();
}

теперь это верно:

Action act = null;
act.SafeInvoke(); // does nothing
act = delegate {Console.WriteLine("hi");}
act.SafeInvoke(); // writes "hi"

В случае событий это имеет то преимущество, что удаляет условие гонки, т.е. вам не нужна временная переменная. Обычно вам нужно:

var handler = SomeEvent;
if(handler != null) handler(this, EventArgs.Empty);

но с помощью:

public static void SafeInvoke(this EventHandler handler, object sender) {
    if(handler != null) handler(sender, EventArgs.Empty);
}

мы можем просто использовать:

SomeEvent.SafeInvoke(this); // no race condition, no null risk

Ответ 2

То, что вы ищете, это Null Conditional (не "коалесцирующий" ) оператор: ?., Он доступен с С# 6.

Ваш пример будет obj?.SomeMethod();. Если obj равно null, ничего не происходит. Когда метод имеет аргументы, например. obj?.SomeMethod(new Foo(), GetBar()); аргументы не вычисляются, если obj равно null, что имеет значение, если оценка аргументов будет иметь побочные эффекты.

И возможно цепочка: myObject?.Items?[0]?.DoSomething()

Ответ 3

Быстрый метод расширения:

    public static void IfNotNull<T>(this T obj, Action<T> action, Action actionIfNull = null) where T : class {
        if(obj != null) {
            action(obj);
        } else if ( actionIfNull != null ) {
            actionIfNull();
        }
    }

Пример:

  string str = null;
  str.IfNotNull(s => Console.Write(s.Length));
  str.IfNotNull(s => Console.Write(s.Length), () => Console.Write("null"));

или, альтернативно:

    public static TR IfNotNull<T, TR>(this T obj, Func<T, TR> func, Func<TR> ifNull = null) where T : class {
        return obj != null ? func(obj) : (ifNull != null ? ifNull() : default(TR));
    }

Пример:

    string str = null;
    Console.Write(str.IfNotNull(s => s.Length.ToString());
    Console.Write(str.IfNotNull(s => s.Length.ToString(), () =>  "null"));

Ответ 4

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

public event EventHandler MyEvent = delegate { };

Нет нулевой проверки.

[ Обновить, благодаря Bevan для указания этого]

Помните о возможном влиянии производительности. Быстрый микро-тест, который я сделал, показывает, что обработка события без подписчиков в 2-3 раза медленнее при использовании шаблона "делегирование по умолчанию". (На моем двухъядерном ноутбуке с тактовой частотой 2,5 ГГц, что означает 279 мс: 785 мс для поднятия 50 миллионов событий без подписки.). Для горячих точек применения это может быть проблемой для рассмотрения.

Ответ 7

Метод расширения Cerating, как предлагается, действительно не решает проблемы с условиями гонки, а скорее скрывает их.

public static void SafeInvoke(this EventHandler handler, object sender)
{
    if (handler != null) handler(sender, EventArgs.Empty);
}

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

Проблема с тем, что возможно, что subsciber события можно было бы назвать ПОСЛЕ того, как он отменил подписку на событие. Это возможно, потому что отказ от подписки может произойти после копирования экземпляра делегата на временную переменную (или переданной как параметр в вышеприведенном методе), но перед вызовом делегата.

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

Единственным известным способом обеспечения безопасности потоков является использование оператора блокировки для отправителя события. Это гарантирует, что все подписки \unsubscriptions\invocation будут сериализованы.

Чтобы быть более точным, блокировка должна применяться к одному и тому же объекту синхронизации, используемому в методах добавления\удаления событий, который по умолчанию имеет значение 'this'.

Ответ 8

Я согласен с ответом Кенни Элиассона. Перейдите с помощью методов расширения. Ниже приведен краткий обзор методов расширения и вашего требуемого метода IfNotNull.

Методы расширения (метод IfNotNull)

Ответ 9

Возможно, не лучше, но, на мой взгляд, более читаемым является создание метода расширения

public static bool IsNull(this object obj) {
 return obj == null;
}

Ответ 10

public static void Call(this Action action)
{
    var safeAction = Interlocked.CompareExchange(ref action, null, null);
    if (safeAction != null)
        safeAction();
}