2015-08-11 15 views
7

Sto usando OrderBy per qualche ordinamento basato sulle proprietà e ho trovato il documentation for the default comparer ma non mi ha spiegato molto. Se un oggetto non implementa System.IComparable<T>, come viene generato un Comparer<T>?Come funziona il comparatore di default in C#?

Ad esempio, sto attualmente ordinando un elenco di oggetti basato su un valore di proprietà di tipo object. Sono sotto i tipi numerici e l'ordinamento funziona correttamente. In che modo C#/Linq sa come ordinare l'oggetto? Fa alcuni un-boxing in primitivi? Fa qualche controllo hash? Come si tradurrebbe in maggiore o minore di?

Se fossero di un tipo più complesso, questo fallirebbe con un errore o sarebbe OrderBy non fare nulla, o sarebbe anche ordinare in un modo che non aveva senso?

+1

Questo post di Jon Skeet può essere utile per te http://codeblog.jonskeet.uk/2011/01/05/reimplementing-linq-to-objects-part-26b-orderby-descending-thenby-descending/ . In realtà, l'intera serie di EduLinq è piacevole da leggere e comprendere una serie di concetti. –

risposta

2

Bene, è possibile controllare la fonte di riferimento e see for yourself quello che fa.

public static Comparer<T> Default { 
     get { 
      Contract.Ensures(Contract.Result<Comparer<T>>() != null); 

      Comparer<T> comparer = defaultComparer; 
      if (comparer == null) { 
       comparer = CreateComparer(); 
       defaultComparer = comparer; 
      } 
      return comparer; 
     } 
    } 
    private static Comparer<T> CreateComparer() { 
     RuntimeType t = (RuntimeType)typeof(T); 

     // If T implements IComparable<T> return a GenericComparer<T> 
#if FEATURE_LEGACYNETCF 
     //(SNITP) 
#endif 
      if (typeof(IComparable<T>).IsAssignableFrom(t)) { 
       return (Comparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericComparer<int>), t); 
      } 

     // If T is a Nullable<U> where U implements IComparable<U> return a NullableComparer<U> 
     if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { 
      RuntimeType u = (RuntimeType)t.GetGenericArguments()[0]; 
      if (typeof(IComparable<>).MakeGenericType(u).IsAssignableFrom(u)) { 
       return (Comparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableComparer<int>), u); 
      } 
     } 
     // Otherwise return an ObjectComparer<T> 
     return new ObjectComparer<T>(); 
    } 

Allora, cosa che fa è controlla se il tipo implementa IComparable<T>, se lo fa utilizza l'operatore di confronto costruito in funzione del tipo (l'elenco degli oggetti che sono i tipi numerici avrebbero seguito questo ramo). Quindi esegue di nuovo lo stesso controllo nel caso in cui il tipo sia Nullable<ICompareable<T>>. Se anche questo non funziona, utilizza lo ObjectComparer che utilizza Comparer.Default.

Here is the Compare code per Comparer.Default

public int Compare(Object a, Object b) { 
     if (a == b) return 0; 
     if (a == null) return -1; 
     if (b == null) return 1; 
     if (m_compareInfo != null) { 
      String sa = a as String; 
      String sb = b as String; 
      if (sa != null && sb != null) 
       return m_compareInfo.Compare(sa, sb); 
     } 

     IComparable ia = a as IComparable; 
     if (ia != null) 
      return ia.CompareTo(b); 

     IComparable ib = b as IComparable; 
     if (ib != null) 
      return -ib.CompareTo(a); 

     throw new ArgumentException(Environment.GetResourceString("Argument_ImplementIComparable")); 
    } 

Come si può vedere controlla se a o b implementa IComparable e se non farlo genera un'eccezione.

+0

Grazie. Ora so di fonte di riferimento – cheft

1

Navigando su Reference Source, restituisce un ObjectComparer<T>, che è un tipo interno speciale che delega semplicemente il lavoro a System.Collections.Comparer.Default.

Questo, a sua volta, genera un'eccezione se riceve parametri che non implementano IComparable. Dal momento che il confronto funziona attraverso downcasting e riflessione, non importa se il tipo statico dell'oggetto non implementa IComparable (che è il caso se si dispone di un elenco di object s).

Quindi la linea di fondo è questa: prima controlla IComparable<T>, quindi controlla IComparable e infine genera un'eccezione.

A proposito, la maggior parte (direi anche tutti i tipi) di tipi built-in implementa IComparable<T> in qualche modo, quindi è così che possono essere ordinati.

0

int, o per essere più precisi, Int32fa in realtà attuare IComparable, quindi funziona. (source)

OrderBy sembra tentare di utilizzare il comparatore per il primo tipo si tratta in tutto, quindi, se si inizia con un oggetto che non implementa IComparable, si ottiene un ArgumentException:

A almeno un oggetto deve implementare IComparable

Se si inizia con diciamo, un Int32, allora si otterrà la stessa eccezione con:

oggetto deve essere di tipo Int32

Dal di confronto per Int32

0

Guardando l'interno, se l'oggetto è generico e implementa IComparable<T>, l'operatore di confronto di default tornerà un'istanza GenericComparer che getta oggetti a quella interfaccia per eseguire confronti. I tipi primitivi lo implementano già. Anche i tipi primitivi di tipo Nullable implementano automaticamente quell'interfaccia, quindi lo NullableComparer restituito funziona allo stesso modo. Non c'è boxing/unboxing in quegli scenari.

Altrimenti, proverà a trasmettere oggetti in istanze non generiche IComparable che possono causare il pugilato con le strutture o lanciare un ArgumentException se il tipo non lo implementa.