Возможные подводные камни использования этого (метод расширения на основе) обсчитывать


15

C# 6 Update

В C#6 ?. is now a language feature:

// C#1-5 
propertyValue1 = myObject != null ? myObject.StringProperty : null; 

// C#6 
propertyValue1 = myObject?.StringProperty; 

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

Оригинал Вопрос:

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

string propertyValue1 = null; 
if(myObject1 != null) 
    propertyValue1 = myObject1.StringProperty; 

int propertyValue2 = 0; 
if(myObject2 != null) 
    propertyValue2 = myObject2.IntProperty; 

И так далее ...

Я использую это так часто, что у меня есть фрагмент для него.

Вы можете сократить это в некоторой степени с инлайн, если:

propertyValue1 = myObject != null ? myObject.StringProperty : null; 

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

propertyValue1 = myObject != null ? 
    (myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null : null; 

То, что я действительно хочу это синтаксис ?? стиль, который отлично работает непосредственно нулевых типов:

int? i = SomeFunctionWhichMightReturnNull(); 
propertyValue2 = i ?? 0; 

Так что я придумал следующее:

public static TResult IfNotNull<T, TResult>(this T input, Func<T, TResult> action, TResult valueIfNull) 
    where T : class 
{ 
    if (input != null) return action(input); 
    else return valueIfNull; 
} 

//lets us have a null default if the type is nullable 
public static TResult IfNotNull<T, TResult>(this T input, Func<T, TResult> action) 
    where T : class 
    where TResult : class 
{ return input.IfNotNull(action, null); } 

Это позволяет мне с нами этот синтаксис:

propertyValue1 = myObject1.IfNotNull(x => x.StringProperty); 
propertyValue2 = myObject2.IfNotNull(x => x.IntProperty, 0); 

//or one with multiple levels 
propertyValue1 = myObject.IfNotNull( 
    o => o.ObjectProp.IfNotNull(p => p.StringProperty)); 

Это упрощает эти вызовы, но я не уверен, что проверка такого рода метод расширения в - он делает код немного легче читать, но за счет расширения объекта. Это будет отображаться во всем, хотя я могу помещать его в специально указанное пространство имен.

Этот пример является довольно простым, немного более сложным будет сравнение двух NULLABLE свойств объекта:

if((obj1 == null && obj2 == null) || 
    (obj1 != null && obj2 != null && obj1.Property == obj2.Property)) 
    ... 

//becomes 
if(obj1.NullCompare(obj2, (x,y) => x.Property == y.Property) 
    ... 

Какие подводные камни использования расширений в этом случае? Можно ли смутить других кодировщиков? Это просто злоупотребление расширениями?


Я предполагаю, что я действительно хочу здесь является расширение компилятор/язык:

propertyValue1 = myObject != null ? myObject.StringProperty : null; 

//becomes 
propertyValue1 = myObject?StringProperty; 

Это сделало бы сложный случай гораздо проще:

propertyValue1 = myObject != null ? 
    (myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null 

//becomes 
propertyValue1 = myObject?ObjectProp?StringProperty; 

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

int? propertyValue2 = myObject?ObjectProp?IntProperty; 

//or 

int propertyValue3 = myObject?ObjectProp?IntProperty ?? 0; 
16

Мы независимо друг от друга пришли с точное имя и реализация метода расширения: Null-propagating extension method. Поэтому мы не считаем, что это запутывает или злоупотребляет методами расширения.

Я хотел бы написать свой пример «несколько уровней» с цепочкой следующим образом:

propertyValue1 = myObject.IfNotNull(o => o.ObjectProp).IfNotNull(p => p.StringProperty); 

Там в now-closed bug on Microsoft Connect, что предложенный «?». как новый оператор C#, который будет выполнять это нулевое распространение. Mads Torgersen (от команды языка C#) кратко объяснил, почему они не будут его реализовывать.

  0

Да, я спросил у Mads прямо у TechEd - в основном эта функция не пропускает разреза, они все равно могут добавить ее в будущая версия C#. 28 мар. 112011-03-28 13:20:02

  0

@ Keith Спасибо за обновление! (Не помешает продолжать напоминать команде C#, что клиенты найдут это полезным.) 29 мар. 112011-03-29 02:33:02

  0

Теперь они всерьез рассматривают это: http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/ at-last-c-is-получение-иногда-называется-safe-navigation-operator.aspx 28 фев. 142014-02-28 15:32:57

+1

Состояние функции «Нулевое распространение» - «Готово». См. «Статус реализации функции языка» Рослина (https://roslyn.codeplex.com/wikipage?title=Language%20Feature%20Status) 25 июл. 142014-07-25 17:01:25

  0

'? .' реализован на C# 6 - я обновил вопрос, чтобы отразить это , Ответы здесь по-прежнему относятся к C# 1-5 15 июн. 152015-06-15 06:41:02


0

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

if(obj1.NullCompare(obj2, (x,y) => x.Property == y.Property) 

Это может быть потому, что у меня нет C# опыт; однако я мог читать и понимать все остальное в вашем коде. Я предпочитаю держать язык кода агностиком (особенно для мелочи), так что завтра другой разработчик может изменить его на совершенно новый язык без слишком большой информации о существующем языке.


11

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


1

Это делает код более легким для чтения, но за счет расширения объекта. Это должно появиться на всем,

Обратите внимание, что вы фактически не расширяете ничего (кроме теоретически).

propertyValue2 = myObject2.IfNotNull(x => x.IntProperty, 0); 

будет генерировать IL код в точности так, как если бы оно было написано:

ExtentionClass::IfNotNull(myObject2, x => x.IntProperty, 0); 

Существует нет «накладных расходов» не было добавлено к объектам, чтобы поддержать это.


5

Как

propertyValue1 = myObject.IfNotNull(o => o.ObjectProp.IfNotNull(p => p.StringProperty)); 

легче читать и писать, чем

if(myObject != null && myObject.ObjectProp != null) 
    propertyValue1 = myObject.ObjectProp.StringProperty; 

Джафар Хусайн отправил образец с использованием Expression Trees для проверки нуль в цепи, Runtime macros in C# 3.

Это, очевидно, имеет последствия для производительности. Теперь, если бы у нас был способ сделать это во время компиляции.

  0

Спасибо, это отличная ссылка. 24 сен. 082008-09-24 08:02:29

+1

Я согласен с вашим первым комментарием. Проблема, которая мне кажется, заключается в том, что из кода не сразу видно, что он делает. Он менее загроможден, но его легче понять, только вы знаете, что он делает. 21 ноя. 082008-11-21 10:34:51


5

Мне просто нужно сказать, что я люблю этот хак!

Я не понял, что методы расширения не подразумевают нулевую проверку, но это имеет смысл. Как отметил Джеймс, вызов метода расширения не является более дорогостоящим, чем обычный метод, однако, если вы делаете тонну этого, то имеет смысл следовать шаблону нулевого объекта, который предложил ljorquera. Или использовать нулевой объект и? вместе.

class Class1 
{ 
    public static readonly Class1 Empty = new Class1(); 
. 
. 
x = (obj1 ?? Class1.Empty).X; 
  0

Методы расширения сами по себе не являются более дорогостоящими, однако этот конкретный метод расширения является более дорогостоящим, поскольку он требует метода лямбда/анонимности. Lambdas скомпилированы до распределения классов в фоновом режиме. Таким образом, он дороже, потому что он требует выделения. 29 янв. 092009-01-29 23:06:37

+1

@JudahHimango Lamdas выполняются только при распределении классов, если они фиксируют переменную (становясь замыканием). Если они этого не делают, они превращаются в статический метод, созданный компилятором ... Вы можете проверить это, посмотрев на скомпилированную DLL с чем-то вроде dotPeek (http://www.jetbrains.com/decompiler/) 24 фев. 142014-02-24 17:50:49


1

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


propertyValue1 = Util.IfNotNull(myObject1, x => x.StringProperty); 
propertyValue2 = Util.IfNotNull(myObject2, x => x.IntProperty, 0); 

«Util.» решетки, но ИМО - меньшее синтаксическое зло.

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


1

Хотя методы расширения обычно вызывают недоразумения при вызове из нулевых экземпляров, я думаю, что цель довольно проста в этом случае.

string x = null; 
int len = x.IfNotNull(y => y.Length, 0); 

Я хотел бы быть уверен, что этот статический метод работает с типами значений, которые могут быть пустыми, например int?

Edit: компилятор говорит, что ни один из них справедливы:

public void Test() 
    { 
     int? x = null; 
     int a = x.IfNotNull(z => z.Value + 1, 3); 
     int b = x.IfNotNull(z => z.Value + 1); 
    } 

Кроме этого, пойти на это.

  0

Вот почему две перегрузки - одна, для которой требуется значение по умолчанию без ограничения на тип результата, и тот, который не требует значения по умолчанию, но ограничивает результат ссылочными типами. 24 сен. 082008-09-24 08:00:20

  0

См. Редактирование для теста. 24 сен. 082008-09-24 10:15:34

  0

Это потому, что int? фактически скомпилирован в Nullable <int>, который на самом деле является структурой. Это всего лишь магия компилятора, которая позволяет сравнить ее с нулевым (она корректно завершает работу с ограничением класса TResult: class). Мне может потребоваться добавить еще одну перегрузку, специфичную для Nullable <T> 26 сен. 082008-09-26 10:53:34

  0

По соображениям созерцания - Nullable <int> имеет только два свойства: HasValue Значение - оба используются в ?? синтаксис. Это не нужно для int? 06 окт. 082008-10-06 18:55:24


15

Вот еще одно решение, для скованными членов, в том числе методы расширения:

public static U PropagateNulls<T,U> (this T obj 
            ,Expression<Func<T,U>> expr) 
{ if (obj==null) return default(U); 

    //uses a stack to reverse Member1(Member2(obj)) to obj.Member1.Member2 
    var members = new Stack<MemberInfo>(); 

    bool  searchingForMembers = true; 
    Expression currentExpression = expr.Body; 

    while (searchingForMembers) switch (currentExpression.NodeType) 
    { case ExpressionType.Parameter: searchingForMembers = false; break; 

      case ExpressionType.MemberAccess:  
      { var ma= (MemberExpression) currentExpression; 
      members.Push(ma.Member); 
      currentExpression = ma.Expression;   
      } break;  

      case ExpressionType.Call: 
      { var mc = (MethodCallExpression) currentExpression; 
      members.Push(mc.Method); 

      //only supports 1-arg static methods and 0-arg instance methods 
      if ( (mc.Method.IsStatic && mc.Arguments.Count == 1) 
       || (mc.Arguments.Count == 0)) 
      { currentExpression = mc.Method.IsStatic ? mc.Arguments[0] 
                : mc.Object; 
       break; 
      } 

      throw new NotSupportedException(mc.Method+" is not supported"); 
     } 

     default: throw new NotSupportedException 
         (currentExpression.GetType()+" not supported"); 
    } 

    object currValue = obj; 
    while(members.Count > 0) 
    { var m = members.Pop(); 

     switch(m.MemberType) 
     { case MemberTypes.Field: 
      currValue = ((FieldInfo) m).GetValue(currValue); 
      break; 

     case MemberTypes.Method: 
      var method = (MethodBase) m; 
      currValue = method.IsStatic 
           ? method.Invoke(null,new[]{currValue}) 
           : method.Invoke(currValue,null); 
      break; 

     case MemberTypes.Property: 
      var method = ((PropertyInfo) m).GetGetMethod(true); 
       currValue = method.Invoke(currValue,null); 
      break; 

     }  

     if (currValue==null) return default(U); 
    } 

    return (U) currValue;  
} 

Тогда вы можете сделать это, где любой может быть пустым, или ни:

foo.PropagateNulls(x => x.ExtensionMethod().Property.Field.Method()); 
+1

Очень проницательный. Спасибо! 18 дек. 082008-12-18 00:58:20

  0

Мне нравится эта идея! Однако он (как и все другие решения здесь), похоже, не работает в заявлении LINQ. Например, при выполнении .Выберите в новый анонимный тип, например .Select (s => new {MyNewProperty = s.PropogateNulls (p => p.Thing)}). Это не работает. Все равно придется использовать старую нулевую проверку. 22 мар. 162016-03-22 23:24:17

  0

Вы должны изменить код, чтобы принять статические методы с двумя аргументами, для 'Enumerable.Select (src, lambda)' 23 мар. 162016-03-23 05:31:48


0

Вот еще одно решение, используя myObject.NullSafe (х => x.SomeProperty.NullSafe (х => x.SomeMethod)), что объясняется в http://www.epitka.blogspot.com/

  0

Спасибо, но это намного больше кода, чтобы сделать то же самое. Также ваш класс Maybe похож на Nullable <T> и с помощью Invoke вы добавляете ненужный удар производительности. Тем не менее - приятно видеть альтернативный подход к той же проблеме. 28 май. 092009-05-28 22:18:56


1

Не ответ на точный вопрос, задаваемый, но is Null-Conditional Operatorin C# 6.0. Я могу утверждать, что это будет плохим выбором для использования опции в ОП, так как C# 6.0 :)

Так ваше выражение является более простым,

string propertyValue = myObject?.StringProperty; 

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

int? propertyValue = myObject?.IntProperty; 

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

int propertyValue = myObject?.IntProperty ?? 0; 

?. не только синтаксис доступен. Для индексированных свойств вы можете использовать ?[..]. Для, например,

string propertyValue = myObject?[index]; //returns null in case myObject is null 

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

var result = value?.Substring(0, Math.Min(value.Length, length)).PadRight(length); 

В этом случае result равна нулю, если value является недействительным и value.Length выражение не приведет к NullReferenceException.