Вызов метода, если не null в C#
можно ли как-то сократить это заявление?
if (obj != null)
obj.SomeMethod();
потому что я часто пишу это, и это становится довольно раздражающим. Единственное, что я могу придумать, это реализовать Null Объект шаблон, но это не то, что я могу делать каждый раз, и это, конечно, не решение для сокращения синтаксиса.
и аналогичная проблема с событиями, где
public event Func<string> MyEvent;
и затем вызвать
if (MyEvent != null)
MyEvent.Invoke();
10 ответов:
начиная с C# 6, Вы можете просто использовать:
MyEvent?.Invoke();
или:
obj?.SomeMethod();
The
?.
является оператором распространения нуля и вызовет.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
то, что вы ищете-это Null Условное (не "слияние") оператор:
?.
. Он доступен с C# 6.ваш пример будет
obj?.SomeMethod();
. Если obj равен null, ничего не происходит. Если метод имеет аргументы, например,obj?.SomeMethod(new Foo(), GetBar());
аргументы не вычисляются, еслиobj
равно null, что имеет значение, если оценка аргументов будет иметь побочные эффекты.и сцепления можно:
myObject?.Items?[0]?.DoSomething()
быстрый метод расширения:
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"));
события могут быть инициализированы пустым делегатом по умолчанию, который никогда не удаляется:
public event EventHandler MyEvent = delegate { };
нет необходимости в нулевой проверке.
[обновление, спасибо Бевану за указание на это]
однако имейте в виду возможное влияние на производительность. Быстрый микро-тест, который я сделал, указывает, что обработка события без подписчиков в 2-3 раза медленнее при использовании шаблона "делегат по умолчанию". (На моем двухъядерном ноутбуке 2,5 ГГц это означает 279 МС : 785ms для привлечения 50 миллионов не подписанных событий.). Для горячих точек приложения это может быть проблемой для рассмотрения.
да, в C# 6.0 -- https://msdn.microsoft.com/en-us/magazine/dn802602.aspx.
object?.SomeMethod()
этой статья Яна Гриффитса дает два разных решения проблемы, которые он заключает, являются аккуратными трюками, которые вы не должны использовать.
метод расширения Cerating, как один из предложенных, на самом деле не решает проблемы с условиями гонки, а скорее скрывает их.
public static void SafeInvoke(this EventHandler handler, object sender) { if (handler != null) handler(sender, EventArgs.Empty); }
как указано этот код является элегантным эквивалентом решения с временной переменной, но...
проблема с том, что возможно, что subsciber события может быть вызван после того, как он отписался от события. Это возможно, потому что отписка может произойти после копирования экземпляра делегата в временная переменная (или передается как параметр в методе выше), но перед вызовом делегата.
В общем случае поведение клиентского кода в таком случае непредсказуемо: состояние компонента уже не позволяет обрабатывать уведомления о событиях. Можно написать клиентский код таким образом, чтобы обрабатывать его, но это поставило бы излишнюю ответственность перед клиентом.
единственный известный способ обеспечить безопасность потока-использовать оператор блокировки для отправителя события. Этот гарантирует, что все подписки\отписки\вызовы будут сериализованы.
чтобы быть более точным, блокировка должна применяться к тому же объекту синхронизации, который используется в методах доступа к событиям add\remove, который по умолчанию "this".
Я согласен с ответом Кенни Элиассон. Перейти с методами расширения. Вот краткий обзор методов расширения и ваш необходимый метод IfNotNull.
может быть, не лучше, но на мой взгляд более читаемым является создание метода расширения
public static bool IsNull(this object obj) { return obj == null; }