2013-04-08 14 views
5

Insomma, sto cercando una guida su quale dei due metodi seguenti dovrebbe essere preferito (e perché):Quando specificare il vincolo `T: IEquatable <T>` anche se non è strettamente necessario?

static IEnumerable<T> DistinctA<T>(this IEnumerable<T> xs) 
{ 
    return new HashSet<T>(xs); 
} 

static IEnumerable<T> DistinctB<T>(this IEnumerable<T> xs) where T : IEquatable<T> 
{ 
    return new HashSet<T>(xs); 
} 
  • argomento a favore di DistinctA: Ovviamente, il vincolo sulla T non è necessaria , perché non lo richiede e poiché le istanze di qualsiasi T sono garantite per essere convertite in System.Object, che fornisce la stessa funzionalità di IEquatable<T> (vale a dire i due metodi Equals e GetHashCode). (Mentre i metodi non generici causano il pugilato con i tipi di valore, non è quello che mi riguarda qui.)

  • Argomento a favore di DistinctB: Il vincolo del parametro generico, sebbene non strettamente necessario, rende visibile ai chiamanti il metodo confronta le istanze di T ed è pertanto un segnale che Equals e GetHashCode dovrebbe funzionare correttamente per T. (Dopo tutto, la definizione di un nuovo tipo senza implementare esplicitamente Equals e GetHashCode accade molto facilmente, quindi il vincolo potrebbe aiutare a catturare alcuni errori all'inizio.)

Domanda: C'è un migliore prassi consolidata e documentata che raccomanda quando specificare questo particolare vincolo (T : IEquatable<T>) e quando no? E se no, uno degli argomenti di cui sopra è viziato in qualche modo? (In tal caso, preferirei argomenti ben congegnati, non solo opinioni personali.)

+0

Dal momento che stai cercando argomenti ben congegnati e non opinioni personali, farò un rapido commento: preferisco la visibilità al chiamante quando cose come questa sono estremamente improbabili da cambiare in futuro. –

risposta

2

Iniziare considerando quando potrebbe importare quale dei due meccanismi è utilizzato; Mi vengono in mente solo due:

  1. Quando il codice viene tradotta in un'altra lingua (o una versione successiva di C#, o un linguaggio correlato come Java, o di un linguaggio completamente diverso, come Haskell). In questo caso, la seconda definizione è chiaramente migliore fornendo al traduttore, sia esso automatico o manuale, maggiori informazioni.
  2. Quando un utente non ha familiarità con il codice lo sta leggendo per imparare come richiamare il metodo. Ancora una volta, credo che il secondo sia chiaramente migliore fornendo più informazioni facilmente a tale utente.

Non riesco a pensare a nessuna circostanza in cui la prima definizione debba essere preferita, e dove in realtà conta al di là delle preferenze personali.

Altri pensieri?

+0

Penso che tutti i requisiti dovrebbero essere dichiarati come vincoli se solo è possibile, perché consente al compilatore di rilevare l'errore all'inizio della compilazione, molto prima del runtime. Tuttavia, posso pensare a un controsenso un po 'inventato che inquinare le vostre interfacce spesso usate con tonnellate di vincoli avrà un impatto negativo sulla durata effettiva della compilazione, poiché il compilatore dovrà analizzare e analizzare più spesso le gerarchie di tipi. Non posso dire Ci credo davvero, e anche se così fosse, userei comunque i vincoli laddove possibile e adeguato e dividerò semplicemente i progetti;) – quetzalcoatl

+0

@quetzalcoatl: Se hai visto parte del mio codice, ti saresti reso conto di quanto sia ironico il mio post; Credo fermamente nei tersenes ogni qualvolta sia ragionevole e pratico. Tuttavia il punto del post sembra essere "Quando la verbosità in più merita di essere presa in considerazione?". –

+0

:) Devo ammettere che adoro il codice per essere breve, ma come un C++ nativo, sono anche un utente pesante del compilatore come strumento di test per la prima volta. Quando ho una scelta tra il rilevamento degli errori in fase di compilazione o un codice più corto più corto un po ', scelgo il primo, specialmente al modulo o al confine del problema. Certo, c'è una soglia. + 50. 150 o più righe solo per bloccare alcuni bug molto naiivi non è un'opzione e la leggibilità e KISS vince :) – quetzalcoatl

3

Mentre il mio commento alla risposta di Pieter è completamente vero, ho ripensato il caso esatto di Distinta a cui ti riferisci.

Questo è un contratto di metodo LINQ, non solo un metodo.

LINQ è pensato per essere una fascade comune implementata da vari provider. Linq2Objects può richiedere un IEquatable, Linq2Sql può richiedere anche IEquatable, ma Linq2Sql potrebbe non richiedere e nemmeno non utilizzare affatto e ignorare completamente IEquatableness come il confronto viene effettuato dal motore DB SQL.

Pertanto, al livello delle definizioni del metodo LINQ, non ha senso specificare il requisito per IEquatable.Limiterà e vincolerà i futuri provider LINQ ad alcune cose di cui non hanno davvero bisogno di occuparsi dei loro domini specifici, e notano che LINQ è in realtà molto spesso tutto relativo alla specificità del dominio, poiché molto spesso le espressioni ei parametri LINQ non sono mai realmente Esegui come codice, ma vengono analizzati e convertiti in altri costrutti come SQL o XPath. L'omissione dei vincoli in questo caso è ragionevole, perché non si può veramente sapere che cosa il proprio provider di dominio futuro-e-sconosciuto avrà davvero bisogno di richiedere da il chiamante. Linq-to-Mushrooms potrebbe dover utilizzare un IToxinEquality anziché IEquatable!

Tuttavia, quando si progettano interfacce/contratti che saranno chiaramente utilizzati come codice eseguibile (non solo alberi di espressione che funzioneranno solo come configurazione o dichiarazioni), quindi non vedo alcun punto valido per non fornire i vincoli.

+0

+1 per l'ultimo paragrafo. Prima di allora, mi chiedevo perché la tua risposta fosse finita in LINQ così tanto ... ma la distinzione tra codice come dati e codice eseguibile ha senso ed è un'altra possibile linea guida. Grazie! – stakx