Domanda

Aggiornamento C # 6

In C # 6 ?. è ora una funzione linguistica :

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

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

La domanda che segue si applica ancora alle versioni precedenti, ma se lo sviluppo di una nuova applicazione utilizzando il nuovo operatore ?. è una pratica molto migliore.

Domanda originale:

Desidero regolarmente accedere alle proprietà su eventuali oggetti null:

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

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

E così via ...

Lo uso così spesso che ho uno snippet per esso.

Puoi accorciarlo in una certa misura con un inline se:

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

Tuttavia, questo è un po 'goffo, specialmente se si impostano molte proprietà o se più di un livello può essere nullo, ad esempio:

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

Quello che voglio davvero è la sintassi dello stile ?? , che funziona benissimo per i tipi direttamente nulli:

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

Quindi ho pensato a quanto segue:

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

Questo mi permette questa sintassi:

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

Questo semplifica queste chiamate, ma non sono sicuro di controllare questo tipo di metodo di estensione - rende il codice un po 'più facile da leggere, ma a costo di estensione dell'oggetto. Questo sembrerebbe su tutto, anche se potrei inserirlo in uno spazio dei nomi con riferimenti specifici.

Questo esempio è piuttosto semplice, un po 'più complesso sarebbe il confronto tra due proprietà oggetto 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 ) 
    ...

Quali sono le insidie ??dell'uso delle estensioni in questo modo? È probabile che altri programmatori siano confusi? È solo un abuso di estensioni?


Suppongo che quello che voglio davvero qui sia un'estensione di compilatore / lingua:

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

//becomes
propertyValue1 = myObject?StringProperty;

Ciò renderebbe il caso complesso molto più semplice:

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

//becomes
propertyValue1 = myObject?ObjectProp?StringProperty;

Funzionerebbe solo per tipi di valore, ma potresti restituire equivalenti nullable:

int? propertyValue2 = myObject?ObjectProp?IntProperty;

//or

int propertyValue3 = myObject?ObjectProp?IntProperty ?? 0;
È stato utile?

Soluzione

Abbiamo creato indipendentemente lo stesso nome e implementazione dello stesso metodo di estensione: Null- metodo di estensione propagante . Quindi non pensiamo che sia confuso o un abuso dei metodi di estensione.

Scriverei i tuoi "più livelli" esempio con concatenamento come segue:

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

Esiste un bug ora chiuso su Microsoft Connect che proposto "?. " come nuovo operatore C # che eseguirà questa propagazione nulla. Mads Torgersen (del team linguistico C #) ha brevemente spiegato perché non lo implementeranno.

Altri suggerimenti

Ecco un'altra soluzione, per i membri concatenati, inclusi i metodi di estensione:

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;    
}

Quindi puoi fare questo dove nessuno può essere nullo o nessuno:

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

Se ti accorgi che devi controllare molto spesso se un riferimento a un oggetto è nullo, potrebbe essere che dovresti usare Null Object Pattern . In questo modello, invece di utilizzare null per gestire il caso in cui non si dispone di un oggetto, si implementa una nuova classe con la stessa interfaccia ma con metodi e proprietà che restituiscono valori predefiniti adeguati.

Com'è

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

più facile da leggere e scrivere di

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

Jafar Husain ha pubblicato un esempio di utilizzo degli alberi delle espressioni per verificare la presenza di null in una catena, Macro di runtime in C # 3 .

Questo ovviamente ha implicazioni sulle prestazioni però. Ora, se solo avessimo un modo per farlo al momento della compilazione.

Devo solo dire che adoro questo hack!

Non avevo capito che i metodi di estensione non implicano un controllo nullo, ma ha perfettamente senso. Come ha sottolineato James, la chiamata del metodo di estensione in sé non è più costosa di un metodo normale, tuttavia se stai facendo una tonnellata di questo, allora ha senso seguire il Null Object Pattern, suggerito da maggioranza. O per usare un oggetto null e ?? insieme.

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

rende il codice un po 'più facile da leggere, ma a costo di estensione dell'oggetto. Questo apparirebbe su tutto,

Nota che non stai estendendo nulla (tranne teoricamente).

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

genererà il codice IL esattamente come se fosse scritto:

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

Non esiste un "overhead" aggiunto agli oggetti per supportare questo.

Al lettore che non lo sa sembra che stai chiamando un metodo su riferimento null. Se lo desideri, ti suggerisco di inserirlo in una classe di utilità anziché utilizzare un metodo di estensione:


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

L'" Util. " gratta, ma l'IMO è il male sintattico minore.

Inoltre, se lo sviluppi come parte di un team, chiedi delicatamente cosa pensano e fanno gli altri. La coerenza tra una base di codice per i modelli utilizzati di frequente è importante.

Mentre i metodi di estensione generalmente causano incomprensioni quando vengono chiamati da istanze nulle, penso che l'intento sia piuttosto semplice in questo caso.

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

Vorrei essere sicuro che questo metodo statico funziona su tipi di valore che possono essere nulli, come int?

Modifica: il compilatore dice che nessuno di questi è valido:

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

A parte questo, provaci.

Non è una risposta alla domanda esatta fatta, ma c'è Operatore nullo condizionale in C # 6.0 . Posso sostenere che sarà una cattiva scelta usare l'opzione in OP dal C # 6.0 :)

Quindi la tua espressione è più semplice,

string propertyValue = myObject?.StringProperty;

Nel caso in cui myObject sia nullo restituisce null. Nel caso in cui la proprietà sia un tipo di valore, devi usare un tipo nullable equivalente, come,

int? propertyValue = myObject?.IntProperty;

Oppure puoi fondere con l'operatore di coalescenza null per dare un valore predefinito in caso di null. Ad esempio,

int propertyValue = myObject?.IntProperty ?? 0;

?. non è l'unica sintassi disponibile. Per le proprietà indicizzate puoi usare ? [..] . Ad esempio,

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

Un comportamento sorprendente dell'operatore ?. è che può bypassare in modo intelligente le successive chiamate .Member se l'oggetto risulta essere nullo. Uno di questi esempi è riportato nel link:

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

In questo caso risultato è null se valore è null e value.Length l'espressione non determinerebbe NullReferenceException .

Personalmente, anche dopo tutte le tue spiegazioni, non riesco a ricordare come diavolo funzioni:

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

Questo potrebbe essere perché non ho esperienza in C #; tuttavia, potrei leggere e comprendere tutto il resto nel tuo codice. Preferisco mantenere il linguaggio del codice agnostico (specialmente per cose banali) in modo che domani un altro sviluppatore possa cambiarlo in una lingua completamente nuova senza troppe informazioni sulla lingua esistente.

Ecco un'altra soluzione che utilizza myObject.NullSafe (x = > x.SomeProperty.NullSafe (x = > x.SomeMethod)), spiegato in http://www.epitka.blogspot.com/

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top