2015-12-27 13 views
9

Con C# 6.0 che posso fare questoCome emulare C# 6 null-condizionale in C# <6

var isEqual = x.Id == y.Id 
       && x.UpdatedAt == y.UpdatedAt 
       && x.Name == y.Name       
       && x.RulesUrl == y.RulesUrl 
       && x.OngoingChallenges?.Count == y.OngoingChallenges?.Count 
       && x.MembershipIds?.Count == y.MembershipIds?.Count; 

C'è qualche soluzione bello fare questo con C# 6.0 <?

voglio dire questa parte

&& x.OngoingChallenges?.Count == y.OngoingChallenges?.Count 
&& x.MembershipIds?.Count == y.MembershipIds?.Count; 

Perché in vecchi progetti non abbiamo possibilità di utilizzare C# 6.0. Come scrivere isEqual in modo efficiente?

+1

No, non c'è. – gdoron

+0

qual è la tua soluzione allora? – senzacionale

+3

@senzacionale, ad esempio operatore ternario come: '(x.MembershipIds == null? (Int?) Null: x.MembershipIds.Count)' – Grundy

risposta

7

x.OnGoingChallenges?.Count equivale a x.OnGoingChallenges != null ? x.OnGoingChallenges.Count : default(int?) (ci sei altri approcci, ma alla fine della giornata è una scorciatoia a null checking chiamato null-condizionale dell'operatore) .

Cioè, il codice non può essere riscritta con un sintatticamente dichiarazione elegante senza C# 6, ma è possibile emulare questa nuova funzione C# 6 utilizzando metodi di estensione ...

public static class StructExtensions 
{ 
    // Check that TProperty is nullable for the return value (this is how C#6's 
    // null-conditional operator works with value types 
    public static TProperty? GetOrDefault<TObject, TProperty>(this TObject someObject, Func<TObject, TProperty> propertySelectionFunc) 
     where TObject : class 
     where TProperty : struct 
    { 
     Contract.Requires(propertySelectionFunc != null); 

     return someObject == null ? default(TProperty?) : propertySelectionFunc(someObject); 
    } 
} 

Ed ora la tua codice in C# 5 apparirebbe come segue:

var isEqual = x.Id == y.Id 
          && x.UpdatedAt == y.UpdatedAt 
          && x.Name == y.Name       
          && x.RulesUrl == y.RulesUrl 
          && x.OngoingChallenges.GetOrDefault(c => c.Count) == y.OngoingChallenges.GetOrDefault(c => c.Count) 
          && x.MembershipIds.GetOrDefault(m => m.Count) == x.MembershipIds.GetOrDefault(m => m.Count); 

Il metodo di estensione insieme potrebbero lavorare per ottenere un valore di proprietà valore digitato o il suo valore di default. È possibile o meno estendere la classe del metodo di estensione per supportare anche il richiamo di un valore del tipo di riferimento o null.

+0

'someObject.Equals (default (TProperty))' non sembra corretto. A parte lanciare una 'NullReferenceException' se' someObject' è 'null', dovresti comparare a' default (TObject) ', non' default (TProperty) '. E siccome hai bisogno di 'TObject: class', sai' default (TObject) 'è' null', a quel punto puoi semplicemente scrivere 'someObject == null'. – hvd

+0

Ottima idea, ma ho ricevuto un errore: non contiene una definizione per 'OngoingChallenges' ... – senzacionale

+0

@senzacionale Oltre alla definizione 'GetOrDefault' che è errata, l'utilizzo di esempio ha anche problemi. Dovrebbe essere 'x.OngoingChallenges.GetOrDefault (c => c.Count)', non 'x.OngoingChallenges.GetOrDefault (c => c.OnGoingChallenges.Count)'. Lo stesso per gli altri usi. :) Questo dovrebbe spiegare l'errore che stai vedendo. – hvd

8

In C# versione < 6.0 si userebbe ternary expressions

var isEqual = x.Id == y.Id 
    && x.UpdatedAt == y.UpdatedAt 
    && x.Name == y.Name       
    && x.RulesUrl == y.RulesUrl 
    && (x.OngoingChallenges == null ? 0 : x.OngoingChallenges.Count) == 
     (y.OngoingChallenges == null ? 0 : y.OngoingChallenges.Count) 
    && (x.MembershipIds == null : 0 ? x.MembershipIds.Count) == 
     (y.MembershipIds == null : 0 : y.MembershipIds.Count); 

Come @Hamlet Hakobyan ha sottolineato, questo non l'equivalente semantico esatta della soluzione originale C# 6.0 utilizzando ?., ma si potrebbe cambiare a (secondo il @hvd):

int? count = x.MembershipIds == null : default(int?) ? x.MembershipIds.Count; 

dipende se si vuole considerare una raccolta mancante e un insieme vuoto come uguali oppure no.


È inoltre possibile utilizzare il null-coalescing operator ?? e fornire un oggetto sostitutivo. Supponendo che gli oggetti sono liste di qualche tipo:

var empty = new List<int>(); 
var isEqual = x.Id == y.Id 
    && x.UpdatedAt == y.UpdatedAt 
    && x.Name == y.Name       
    && x.RulesUrl == y.RulesUrl 
    && (x.OngoingChallenges ?? empty).Count == (y.OngoingChallenges ?? empty).Count 
    && (x.MembershipIds ?? empty).Count == (y.MembershipIds ?? empty).Count; 
+3

La prima soluzione ha un problema. Presuppone che 'nullo uguale a 0' che è un'ipotesi errata. –

+0

La lettura della proprietà 'OngoingChallenges' due volte può rappresentare un problema in termini di prestazioni se crea una nuova lista (o qualsiasi altro tipo restituisca) ogni volta che la proprietà viene letta.Se è destinato a essere un elenco di sola lettura, ma ha come target una versione di framework che non ha 'ReadOnlyCollection', allora potrebbe essere una buona ragione per la quale restituirebbe ogni volta una nuova lista. La tua seconda opzione, con 'empty', evita questo problema. (Nit-pick: non si farebbe riferimento a 'a == b' come" usereste un'espressione binaria ", si menzionerebbe l'operatore di uguaglianza per nome.'?: 'Si chiama l'operatore condizionale.) – hvd

+1

@ HamletHakobyan's il punto è anche giusto. Questo può essere facilmente corretto cambiando '0' in' default (int?) '/' (Int?) Null'. – hvd

2

Prima di C# 6, ho usato qualcosa di simile

public static class CommonExtensions 
{ 
    public static TValue TryGet<TObject, TValue>(this TObject obj, Func<TObject, TValue> getter, TValue defaultValue = default(TValue)) 
     where TObject : class 
    { 
     return obj == null ? defaultValue : getter(obj); 
    } 

    //If objects types are equals 
    public static bool KeyEquals<TObject, TValue>(this TObject a, TObject b, Func<TObject, TValue> keyGetter) 
     where TObject : class 
    { 
     return a != null 
      && b != null 
      && EqualityComparer<TValue>.Default.Equals(keyGetter(a), keyGetter(b)); 
    } 
} 



var isEqual = x.Id == y.Id 
       && x.UpdatedAt == y.UpdatedAt 
       && x.Name == y.Name       
       && x.RulesUrl == y.RulesUrl 
       //v1 
       && x.OngoingChallenges.TryGet(v => v.Count) == y.OngoingChallenges.TryGet(v => v.Count) 
       //v2 
       && x.MembershipIds.KeyEquals(y.MembershipIds, v => v.Count);