2013-03-19 10 views
6

Ho letto varie domande simili alle mie ma nessuna di esse sembra risolvere il mio problema.Implement IEable <T> quando T potrebbe essere IEnumerable <T>

ho un tipo come questo:

class MyObject<T> : IEquatable<MyObject<T>> { // no generic constraints 
    private readonly string otherProp; 
    private readonly T value; 

    public MyObject(string otherProp, T value) 
    { 
    this.otherProp = otherProp; 
    this.value = value; 
    } 

    public string OtherProp { get { return this.otherProp; } } 

    public T Value { get { return this.value; } } 

    // ... 

    public bool Equals(MyObject<T> other) 
    { 
    if (other == null) 
    { 
     return false; 
    } 

    return this.OtherProp.Equals(other.OtherProp) && this.Value.Equals(other.Value); 
    } 
} 

Quando T è uno scalare come MyObject<int> uguaglianza funziona correttamente, ma quando ho definito qualcosa di simile MyObject<IEnumerable<int>> uguaglianza non riesce.

Il motivo è che quando T è IEnumerable<T> dovrei chiamare this.Value.SequenceEqual(other.Value).

Gestione di questa differenza a gonfie dimensioni Equals(MyObject<T>) con troppo LOC di tipo check e reflection (per me, che porta a una violazione di SOLID/SRP).

Non sono stato in grado di trovare questo caso specifico nelle linee guida MSDN, quindi se qualcuno ha già affrontato questo problema; sarebbe bello se questa conoscenza potesse essere condivisa.

Edit: Alternative

Per K.I.S.S., mi chiedo a fare qualcosa di simile:

class MyObject<T> : IEquatable<MyObject<T>> { 
    private readonly IEnumerable<T> value; 

    // remainder omitted 
} 

In questo modo l'implementazione di Equal sarà molto semplice. E quando ho bisogno di un solo valore, ho una collezione di 1 oggetto. Ovviamente T non sarà un enumerabile (ma la struttura dati è privata quindi non ci sono problemi).

+0

Se è possibile nel tuo caso, puoi anche aggiungere un vincolo generico 'dove T: IEquatable '. – Guillaume

+0

@Guillaume, devo valutare e ragionare su di esso ... – jay

+0

Per riferimento, voglio aggiungere quello che va nella risposta sotto __Edit: Alternative__ era vicino alla mia vera soluzione. Sto sperimentando che non c'è motivo valido per non usare un 'IEnumerable ' anche quando nella maggior parte dei casi la raccolta di 'T' manterrà solo un elemento o piuttosto sarà vuota. Questo non è il caso, la collezione spesso contiene da uno a pochi elementi; quindi direi che 'IEnumerable ' dovrebbe essere _hard coded_ nella definizione di 'MyObject ' piuttosto che facoltativamente passato nel suo insieme attraverso il parametro type (sto parlando di evitare 'MyObject >' e vai per l'alternativa). – jay

risposta

3

si potrebbe avere il MyObject<T> fare un confronto di uguaglianza per il tipo T e l'uso che:

class MyObject<T> : IEquatable<MyObject<T>> 
{ 
    private readonly IEqualityComparer<T> comparer; 
    public MyObject(string otherProp, T value, IEqualityComparer<T> comparer) 
    { 
     this.comparer = comparer; 
    } 
    public MyObject(string otherProp, T value) 
     : this(otherProp, value, EqualityComparer<T>.Default) 
    { 
    } 

    public bool Equals(MyObject<T> other) 
    { 
     return OtherProp.Equals(other.OtherProp) && comparer.Equals(this.Value, other.Value); 
    } 
} 

Poi per IEnumerable<T> è possibile utilizzare un operatore di confronto che confronta le sequenze invece dei riferimenti.Potresti voler utilizzare i metodi di fabbrica per creare i tuoi oggetti per garantire che lo stesso tipo di comparatore venga utilizzato per lo stesso T per garantire che l'uguaglianza resti simmetrica.

+0

+1, codice ben scritto. Giusto per indicarlo, questo funzionerebbe automaticamente per tipi come 'int' (dato che è menzionato nella domanda)? – Default

+0

@Lee +1 per factory fornito con 'constructor injection'; ma come affermato in un altro commento e nella mia ultima modifica vorrei evitare di scrivere codice infrastrutturale. – jay

+0

@Lee, contrassegnato come risposta perché come indicato nel commento precedente mi è piaciuto l'uso di C.I. passare la dipendenza. – jay

1

Non è necessaria alcuna riflessione. Una possibilità è quello di verificare se il valore è IEnumerable o no nel vostro metodo Equals:

IEnumerable e = other.Value as IEnumerable; 

if(e != null){ 
// use SequenceEqual 
}else{ 
// use Equals 
} 
+0

Hai provato questa soluzione, @alex? Penso che non sia così semplice come presentato qui. 'SequenceEqual' ha bisogno di un parametro di tipo quindi devi castare su' IEnumerable '. Per dire di più l'istanza attuale può essere uno scalare o una enumerabile di un T. diverso. Un numero molto maggiore di controlli è implicato e non penso che sia possibile ignorare la riflessione. C'è di più: 'string' dovrebbe essere trattato come uno scalare ma anche' stringa' implementa IEnumerable . – jay

+0

È possibile scrivere un metodo di confronto di uguaglianza "universale" che prende due oggetti. Se sono entrambi enumerabili, allora itera su di loro. Per confrontare gli elementi di IEnumerable si chiama da solo. Quindi array di array verrebbero confrontati correttamente. È anche possibile aggiungere un'eccezione alla stringa, come ottimizzazione delle prestazioni. – alex

+0

scusate ma voglio mantenere le cose semplici. Non scriverò e testerò un codice infrastrutturale così generale. Come puoi vedere dalla mia ultima modifica alla domanda, sto cercando un K.I.S.S. soluzione. – jay

4

Nel caso in cui T è IEnumerable<T>, il codice a confronto due riferimenti di IEnumerable<T> e, naturalmente, tali riferimenti potrebbero non essere uguali. In realtà, si otterrà questo comportamento, quando T sarà qualsiasi tipo di riferimento senza il metodo Equals sostituito.

Se non si desidera confrontare i riferimenti qui, è necessario scrivere un codice, che confronterà queste sequenze in base al loro contenuto. Tuttavia, dovresti notare che quella sequenza potrebbe essere infinita.

+0

Stavo per modificare il titolo ... Ma ora vedo che l'ho taggato correttamente con 'structural-ugality'. – jay

1

questo è il caso in cui si dovrebbe prendere l'aiuto di classe helper, il tipo MyObject può fare uso di EqualityChecker individuale.

Direi che implementare un modello di strategia con metodo di produzione statico semplificherà la progettazione.

qualcosa di simile sotto

class MyObject<T> : IEquatable<MyObject<T>> 
{ // no generic constraints 
    private readonly string otherProp; 
    private readonly T value; 

    public MyObject(string otherProp, T value) 
    { 
     this.otherProp = otherProp; 
     this.value = value; 
    } 

    public string OtherProp { get { return this.otherProp; } } 

    public T Value { get { return this.value; } } 

    // ... 

    public bool Equals(MyObject<T> other) 
    { 
     if (other == null) 
     { 
      return false; 
     } 

    var cheker = EqualityChekerCreator<T>.CreateEqualityChecker(other.Value); 

    if (cheker != null) 
     return cheker.CheckEquality(this.Value, other.value); 

     return this.OtherProp.Equals(other.OtherProp) && this.Value.Equals(other.Value); 
    } 
} 

public static class EqualityChekerCreator<T> 
{ 
    private static IEqualityCheker<T> checker; 

    public static IEqualityCheker<T> CreateEqualityChecker(T type) 
    { 
     var currenttype = type as IEnumerable<T>; 

     if(currenttype!=null) 
      checker = new SequenceEqualityChecker<T>(); 

     return checker; 
    } 
} 

public interface IEqualityCheker<in T> 
{ 
    bool CheckEquality(T t1, T t2); 
} 

public class SequenceEqualityChecker <T> : IEqualityCheker<T> 
{ 
    #region Implementation of IEqualityCheker<in T> 

    public bool CheckEquality(T t1, T t2) 
    { 
     // implement method here; 
    } 

    #endregion 
} 
Problemi correlati