Возможные ловушки использования этого (основанного на методе расширения) сокращения - PullRequest
15 голосов
/ 23 сентября 2008

Обновление C # 6

In C # 6 ?. теперь является языковой функцией :

// 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 ) );

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

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

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;

Ответы [ 11 ]

16 голосов
/ 10 октября 2008

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

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

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

В Microsoft Connect есть закрытая ошибка , которая теперь предлагает "?". как новый оператор C #, который будет выполнять это нулевое распространение. Мэдс Торгерсен (Mads Torgersen) из команды языка C # кратко объяснил, почему они не будут его реализовывать.

15 голосов
/ 28 сентября 2008

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

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());
11 голосов
/ 23 сентября 2008

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

5 голосов
/ 24 сентября 2008

Я просто должен сказать, что мне нравится этот хак!

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

class Class1
{
    public static readonly Class1 Empty = new Class1();
.
.
x = (obj1 ?? Class1.Empty).X;
5 голосов
/ 24 сентября 2008

Как это

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

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

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

Джафар Хусейн опубликовал пример использования деревьев выражений для проверки наличия нулей в цепочке, Макросы времени выполнения в C # 3 .

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

1 голос
/ 07 января 2015

Не является ответом на точный заданный вопрос, но есть Нулевой оператор в C # 6.0 . Я могу утверждать, что это плохой выбор для использования опции в OP, так как 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.

1 голос
/ 24 сентября 2008

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

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

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

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

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

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

1 голос
/ 24 сентября 2008

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


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

"Утил". решает, но является ИМО меньшим синтаксическим злом.

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

1 голос
/ 24 сентября 2008

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

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

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

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

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

Не добавлено «накладных расходов» на объекты для поддержки этого.

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

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

...