Вызов метода, если не ноль в C # - PullRequest
81 голосов
/ 16 мая 2009

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

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

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

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

public event Func<string> MyEvent;

, а затем вызвать

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

Ответы [ 10 ]

134 голосов
/ 16 мая 2009

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

MyEvent?.Invoke();

или

obj?.SomeMethod();

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

===

До C # 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
26 голосов
/ 26 сентября 2014

Требуется оператор Нулевое условие (не "объединение"): ?.. Это доступно с C # 6.

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

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

10 голосов
/ 04 марта 2014

Метод быстрого расширения:

    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 голосов
/ 21 марта 2015

Да, в C # 6.0 - https://msdn.microsoft.com/en-us/magazine/dn802602.aspx.

object?.SomeMethod()
4 голосов
/ 16 мая 2009

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

public event EventHandler MyEvent = delegate { };

Проверка нуля не требуется.

[ Обновление , спасибо Бевану за указание на это]

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

2 голосов
/ 18 мая 2012

Я согласен с ответом Кенни Элиассона. Перейти с методами расширения. Вот краткий обзор методов расширения и вашего необходимого метода IfNotNull.

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

2 голосов
/ 17 мая 2009

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

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

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

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

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

Единственным известным способом обеспечения безопасности потока является использование оператора блокировки для отправителя события. Это обеспечивает сериализацию всех подписок \ отписок \ вызова.

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

2 голосов
/ 16 мая 2009

Эта статья Иана Гриффитса дает два разных решения проблемы, которые, как он заключает, являются изящными приемами, которые вы не должны использовать.

0 голосов
/ 17 мая 2009

Для этого в C # существует малоизвестный нулевой оператор. Может быть полезно:

http://weblogs.asp.net/scottgu/archive/2007/09/20/the-new-c-null-coalescing-operator-and-using-it-with-linq.aspx

0 голосов
/ 16 мая 2009

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

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