2014-09-19 19 views
6

In primo luogo ho visto IEqualityComparer for anonymous type e le risposte non ci non rispondere alla mia domanda, per l'ovvia ragione che ho bisogno di un IEqualityComparer non e IComparer per l'uso con il metodo di Linq Distinct(). Ho controllato le altre risposte troppo e questi a corto di una soluzione ...IEqualityComparer per tipo Annoymous

Il problema

ho qualche codice per manipolare e tirare record in da un DataTable

var glext = m_dtGLExt.AsEnumerable(); 
var cflist = 
    (from c in glext 
    orderby c.Field<string>(m_strpcCCType), 
      c.Field<string>(m_strpcCC), 
      c.Field<string>(m_strpcCCDesc), 
      c.Field<string>(m_strpcCostItem) 
    select new 
    { 
     CCType = c.Field<string>(m_strpcCCType), 
     CC = c.Field<string>(m_strpcCC), 
     CCDesc = c.Field<string>(m_strpcCCDesc), 
     CostItem = c.Field<string>(m_strpcCostItem) 
    }).Distinct(); 

ma Ho bisogno che il metodo distinto sia insensibile al maiuscolo/minuscolo. Quello che mi sta buttando qui è l'uso di tipi anonimi.

tentata soluzione 1

Se avessi SomeClass che aveva oggetti concreti ho potuto ovviamente fare

public class SumObject 
{ 
    public string CCType { get; set; } 
    public string CC { get; set; } 
    public string CCDesc { get; set; } 
    public string CostItem { get; set; } 
} 

ho potuto ovviamente fare questo

List<SumObject> lso = new List<SumObject>() 
{ 
    new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Rooney", CostItem = "I477" }, 
    new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Zidane", CostItem = "I677" }, 
    new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Falcao", CostItem = "I470" }, 
}; 
var e = lso.Distinct(new SumObjectComparer()); // Great :] 

dove

class SumObjectComparer : IEqualityComparer<SumObject> 
{ 
    public bool Equals(SumObject x, SumObject y) 
    { 
     if (Object.ReferenceEquals(x, y)) 
      return true; 
     if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) 
      return false; 
     return x.CCType.CompareNoCase(y.CCType) == 0 && 
       x.CC.CompareNoCase(y.CC) == 0 && 
       x.CCDesc.CompareNoCase(y.CCDesc) == 0 && 
       x.CostItem.CompareNoCase(y.CostItem) == 0; 
    } 

    public int GetHashCode(SumObject o) 
    { 
     if (Object.ReferenceEquals(o, null)) 
      return 0; 
     int hashCCType = String.IsNullOrEmpty(o.CCType) ? 
      0 : o.CCType.ToLower().GetHashCode(); 
     int hashCC = String.IsNullOrEmpty(o.CC) ? 
      0 : o.CC.ToLower().GetHashCode(); 
     int hashCCDesc = String.IsNullOrEmpty(o.CCDesc) ? 
      0 : o.CCDesc.ToLower().GetHashCode(); 
     int hashCostItem = String.IsNullOrEmpty(o.CostItem) ? 
      0 : o.CostItem.ToLower().GetHashCode(); 
     return hashCCType^hashCC^hashCCDesc^hashCostItem; 
    } 
} 

Tuttavia, l'utilizzo di tipi anonimi nella query Linq sopra mi sta lanciando.

Tentativo Soluzione 2

Per tentare un'altra soluzione a questo (e perché ho lo stesso problema altrove) ho generato la seguente classe di confronto generico

public class GenericEqualityComparer<T> : IEqualityComparer<T> 
{ 
    Func<T, T, bool> compareFunction; 
    Func<T, int> hashFunction; 

    public GenericEqualityComparer(Func<T, T, bool> compareFunction, Func<T, int> hashFunction) 
    { 
     this.compareFunction = compareFunction; 
     this.hashFunction = hashFunction; 
    } 

    public bool Equals(T x, T y) { return compareFunction(x, y); } 
    public int GetHashCode(T obj) { return hashFunction(obj); } 
} 

modo che io possa tentare di fare

var comparer = new GenericEqualityComparer<dynamic>(
    (x, y) => { /* My equality stuff */ }, 
    o => { /* My hash stuff */ }); 

ma questo getta il valore restituito come IEnumerable<dynamic> che a sua volta effettua il mio prossimo utilizzo di cflist, in modo che in una seguente query lo join non riesca.

var cf = 
    (from o in cflist 
    join od in glext 
    on new { o.CCType, o.CC, o.CCDesc, o.CostItem } equals new 
    { 
     CCType = od.Field<string>(m_strpcCCType), 
     CC = od.Field<string>(m_strpcCC), 
     CCDesc = od.Field<string>(m_strpcCCDesc), 
     CostItem = od.Field<string>(m_strpcCostItem) 
    } 
    into c 
    select new { ... } 

Non voglio entrare in brutto fusione da e per IEnumerable<T> s a causa del pesante uso di questo codice ...

Domanda

C'è un modo posso crea il mio IEquailityComparer per i miei tipi anonimi?

Grazie per il vostro tempo.

+0

La prima soluzione avrebbe funzionato se si fosse sostituito l'oggetto anonimo 'select new' con un' select new SumObject' object. Qual è il problema con l'utilizzo di un oggetto anonimo, se hai già creato una classe con nome per questo? – dasblinkenlight

+0

Non ho e non posso, questo è il punto. Se potessi, sarebbe facile. Ho anche molti altri posti in cui queste distinte operazioni funzionano con le query selezionate di linq e i tipi anonimi, quindi mi piacerebbe una soluzione più generale ... – MoonKnight

+0

Sarà lento, ma suppongo che dovrai usare il reflection per scrivere un IEqualityComparer e confronta tutti i campi e le proprietà. –

risposta

11

C'è un modo per creare un mio IEquailityComparer per i miei tipi anonimi?

Sicuro. Hai solo bisogno di usare inferenza di tipo.Ad esempio, si potrebbe avere qualcosa di simile:

public static class InferredEqualityComparer 
{ 
    public static IEqualityComparer<T> Create<T>(
     IEnumerable<T> example, 
     Func<T, T, bool> equalityCheck, 
     Func<T, int> hashCodeProvider) 
    { 
     return new EqualityComparerImpl<T>(equalityCheck, hashCodeProvider); 
    } 

    private sealed class EqualityComparerImpl<T> : IEqualityComparer<T> 
    { 
     // Implement in the obvious way, remembering the delegates and 
     // calling them appropriately. 
    } 
} 

Poi:

var glext = m_dtGLExt.AsEnumerable(); 
var query = from c in glext 
      orderby ... 
      select new { ... }; 
var comparer = InferredEqualityComparer.Create(query, 
    (x, y) => { ... }, 
    o => { ... } 
); 
var distinct = query.Distinct(comparer); 

In sostanza il primo parametro per il metodo è solo utilizzato per inferenza di tipo, in modo che il compilatore può capire che tipo da usare per i parametri di espressione lambda.

Si potrebbe creare l'operatore di confronto prima del tempo con la creazione di un campione del tipo anonimo:

var sample = new[] { new { ... } }; 
var comparer = InferredExqualityComparer.Create(sample, ...); 
var distinct = (... query here ...).Distinct(comparer); 

ma poi ogni volta che si cambia la query hai avuto modo di cambiare il campione troppo.

+1

Dammit Skeet Stavo per pubblicare qualcosa proprio così. –

+0

Ciao Jon, grazie per la risposta, ma potrebbe esserci qualche incomprensione qui. Non ho l'oggetto 'lso' che hai fatto riferimento, stavo semplicemente usando questo come esempio di cosa potrei fare usando' Distinct() 'con un tipo concreto. Non vedo come posso utilizzare la soluzione di cui sopra con la query LINQ nel primo frammento di codice della domanda ... – MoonKnight

+0

@ Killercam: Modificato. Fondamentalmente è necessario ottenere prima la versione "non distinta", quindi creare il comparatore, quindi chiamare "Distinct". –

1

This post può ottenere quello che vuoi. Anche se per .NET 2.0 funziona anche per le versioni più recenti (vedere la parte inferiore di questo post per come ottenere ciò). A differenza della soluzione di Jon Skeets, non usiamo un metodo di produzione come creare. Ma penso che sia solo lo zucchero sintattico.

+0

Grazie per la risposta:]. – MoonKnight

Problemi correlati