2014-09-19 11 views
6

Zum einen habe ich gesehen, IEqualityComparer for anonymous type und die Antworten dort nicht beantwortet meine Frage, aus dem offensichtlichen Grund, die ich brauche eine IEqualityComparer nicht und IComparer für die Verwendung mit Distinct() Methode des Linq. Ich habe die anderen Antworten zu überprüfen und diese ermangeln einer Lösung ...IEqualityComparer für Annoymous Typ

Das Problem

ich einige Code haben zu manipulieren und ziehen Datensätze in einer 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(); 

aber Ich brauche die eindeutige Methode, um zwischen Groß- und Kleinschreibung zu unterscheiden. Was mich hierher wirft, ist die Verwendung anonymer Typen.

Versuchte Lösung 1

Wenn ich SomeClass hatte, die ich konkrete Gegenstände hatte offensichtlich

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

Ich könnte natürlich tun dies

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 :] 

wo

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; 
    } 
} 

Allerdings wirft mich die Verwendung von anonymen Typen in der obigen Linq-Abfrage.

Versuchte Lösung 2

Um eine andere Lösung dieses Problems zu versuchen (und weil ich habe das gleiche Problem an anderer Stelle) I erzeugt die folgende allgemeine comparer Klasse

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); } 
} 

, so dass ich versuchen könnte zu tun

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

aber dies wirft den zurückgegebenen Wert als IEnumerable<dynamic> was wiederum Auswirkungen meiner bevorstehenden Verwendung von cflist, so dass in einer folgenden Abfrage die join schlägt fehl.

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 { ... } 

Ich will nicht wegen der schweren Verwendung dieses Codes zu und von IEnumerable<T> s in hässliches Gießen erhalten ...

Frage

Gibt es eine Möglichkeit kann ich Erstelle mein ein IEquailityComparer für meine anonymen Typen?

Danke für Ihre Zeit.

+0

Ihre erste Lösung hätte funktioniert, wenn Sie Ihr anonymer 'Select new' -Objekt durch ein 'Select new SumObject'-Objekt ersetzt haben. Was ist mit der Verwendung eines anonymen Objekts wichtig, wenn Sie bereits eine benannte Klasse dafür erstellt haben? – dasblinkenlight

+0

Ich habe nicht und kann nicht, das ist der Punkt. Wenn ich könnte, wäre es einfach. Ich habe auch mehrere andere Orte, wo diese unterschiedliche Operation mit linq Select-Abfragen und anonyme Typen arbeiten, so würde ich eine allgemeinere Lösung wünschen ... – MoonKnight

+0

Es wird langsam sein, aber ich denke, Sie müssen Reflektion verwenden, um eine IEqualityComparer und zu schreiben vergleiche alle Felder und Eigenschaften. –

Antwort

11

Gibt es eine Möglichkeit, meine IEquailityComparer für meine anonymen Typen zu erstellen?

Sicher. Sie müssen nur Typinferenz verwenden.

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. 
    } 
} 

Dann:

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); 

Grundsätzlich ist der erste Parameter der Methode ist nur verwendet für Typinferenz, so dass der Compiler arbeiten können, welche Art Sie zum Beispiel so etwas wie haben könnte für die Lambda-Expressionsparameter zu verwenden.

Sie könnte schaffen die comparer vor der Zeit durch eine Probe des anonymen Typs zu schaffen:

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

aber dann jedes Mal, wenn Sie die Abfrage ändern Sie haben auch die Probe zu ändern.

+1

Dammit Skeet Ich wollte gerade so etwas posten. –

+0

Hallo Jon, danke für die Antwort, aber hier kann es zu Missverständnissen kommen. Ich habe das "lso" -Objekt, auf das Sie verwiesen haben, nicht. Ich habe es lediglich als Beispiel dafür verwendet, was ich mit 'Distinct()' mit einem konkreten Typ machen könnte. Ich sehe nicht, wie ich die obige Lösung mit der LINQ-Abfrage im ersten Code-Snippet der Frage verwenden kann ... – MoonKnight

+0

@Killercam: Bearbeitet. Grundsätzlich müssen Sie zuerst die "nicht-distinktive" Version erstellen, dann den Vergleicher erstellen und dann "Distinct" aufrufen. –

1

This post kann bekommen, was Sie wollen. Obwohl für .NET 2.0, funktioniert es auch für neuere Versionen (siehe unten in diesem Beitrag, um dies zu erreichen). Im Gegensatz zur Jon Skeets Lösung werden wir keine Factory-Methode wie create verwenden. Aber das ist nur syntaktischer Zucker, denke ich.

+0

Danke für die Antwort:]. – MoonKnight