Distinct nicht mit LINQ to Objects arbeiten
-
21-09-2019 - |
Frage
class Program
{
static void Main(string[] args)
{
List<Book> books = new List<Book>
{
new Book
{
Name="C# in Depth",
Authors = new List<Author>
{
new Author
{
FirstName = "Jon", LastName="Skeet"
},
new Author
{
FirstName = "Jon", LastName="Skeet"
},
}
},
new Book
{
Name="LINQ in Action",
Authors = new List<Author>
{
new Author
{
FirstName = "Fabrice", LastName="Marguerie"
},
new Author
{
FirstName = "Steve", LastName="Eichert"
},
new Author
{
FirstName = "Jim", LastName="Wooley"
},
}
},
};
var temp = books.SelectMany(book => book.Authors).Distinct();
foreach (var author in temp)
{
Console.WriteLine(author.FirstName + " " + author.LastName);
}
Console.Read();
}
}
public class Book
{
public string Name { get; set; }
public List<Author> Authors { get; set; }
}
public class Author
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
return true;
//if (obj.GetType() != typeof(Author)) return false;
//else return ((Author)obj).FirstName == this.FirstName && ((Author)obj).FirstName == this.LastName;
}
}
Dies basiert auf einem Beispiel in "LINQ in Action". Listing 4.16.
Dieser druckt Jon Skeet zweimal. Warum? Ich habe sogar Equals-Methode in der Klasse Autor versucht, zu überschreiben. Noch Distinct scheint nicht zu arbeiten. Was bin ich?
Edit: Ich habe hinzugefügt == und! = Operator zu überlasten. Noch immer keine Hilfe.
public static bool operator ==(Author a, Author b)
{
return true;
}
public static bool operator !=(Author a, Author b)
{
return false;
}
Lösung
LINQ Distinct ist nicht so schlau, wenn es um benutzerdefinierte Objekte kommt.
Alles, was es tut, ist Blick auf Ihre Liste und sehen, dass es zwei verschiedene Objekte hat (es ist nicht, dass sie die gleichen Werte haben für die Mitgliedsfelder schert).
Eine Abhilfe ist, die IEquatable Schnittstelle zu implementieren, wie gezeigt hier .
Wenn Sie Ihren Autor Klasse wie ändern, so dass es funktionieren soll.
public class Author : IEquatable<Author>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
public override int GetHashCode()
{
int hashFirstName = FirstName == null ? 0 : FirstName.GetHashCode();
int hashLastName = LastName == null ? 0 : LastName.GetHashCode();
return hashFirstName ^ hashLastName;
}
}
Andere Tipps
Die Distinct()
Methode überprüft Referenz Gleichheit für Referenztypen. Dies bedeutet, es wird buchstäblich das gleiche Objekt suchen dupliziert, nicht verschiedene Objekte, die die gleichen Werte enthalten.
Es gibt eine Überlastung , die eine IEqualityComparer , so dass Sie andere Logik zu bestimmen, ob ein bestimmtes Objekt gleich einem anderen angeben.
Wenn Sie möchten, Autor normalerweise wie ein normales Objekt verhalten (das heißt nur Referenz Gleichheit), aber für die Zwecke der Distinct Prüfung Gleichstellung von Namenswerte, verwenden Sie einen IEqualityComparer . Wenn Sie wollen immer Autor im Vergleich zu den Namenswerte werden Objekte basierend, dann überschreiben GetHashCode und Equals oder implementieren IEquatable .
Die beiden Elemente auf der IEqualityComparer
Schnittstelle sind Equals
und GetHashCode
. Ihre Logik zu bestimmen, ob zwei Author
Objekte sind gleich zu sein scheint, wenn der erste und Nachname Strings gleich sind.
public class AuthorEquals : IEqualityComparer<Author>
{
public bool Equals(Author left, Author right)
{
if((object)left == null && (object)right == null)
{
return true;
}
if((object)left == null || (object)right == null)
{
return false;
}
return left.FirstName == right.FirstName && left.LastName == right.LastName;
}
public int GetHashCode(Author author)
{
return (author.FirstName + author.LastName).GetHashCode();
}
}
Eine andere Lösung ohne Umsetzung IEquatable
, Equals
und GetHashCode
ist die LINQs GroupBy
Methode zu verwenden, und das erste Element aus der IGrouping wählen.
var temp = books.SelectMany(book => book.Authors)
.GroupBy (y => y.FirstName + y.LastName )
.Select (y => y.First ());
foreach (var author in temp){
Console.WriteLine(author.FirstName + " " + author.LastName);
}
Es gibt eine weitere Möglichkeit, verschiedene Werte aus der Liste des benutzerdefinierten Datentypen zu erhalten:
YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();
Sicherlich wird es unterschiedliche Menge von Daten geben
Distinct()
führt den Standardgleichheitsvergleich auf Objekte in den zählbaren. Wenn Sie nicht außer Kraft gesetzt Equals()
und GetHashCode()
, dann verwendet er die Standardimplementierung auf object
, die Referenzen vergleicht .
Die einfache Lösung ist eine richtig Implementierung von Equals()
und GetHashCode()
für alle Klassen, die im Objektgraphen teilnehmen zu können (dh Buch und Autor) vergleichen.
Die Sie haben überschriebenen Equals (), aber stellen Sie sicher, dass Sie auch GetHashCode () IEqualityComparer
Schnittstelle ist eine Bequemlichkeit, die Sie implementieren können < a href = "http://msdn.microsoft.com/en-us/library/7h9bszxx(VS.80).aspx" rel = "noreferrer"> Equals()
und
Die oben genannten Antworten sind falsch !!! Distinct wie auf MSDN gibt die Standard-Äquators angegeben, die wie angegeben Die Default-Eigenschaft überprüft, ob Typ T implementiert die System.IEquatable Schnittstelle, und wenn ja, gibt ein EqualityComparer dass Verwendungen, dass die Umsetzung. Ansonsten kehrt ein EqualityComparer, die die Überschreibungen von Object.Equals verwendet und Object.GetHashCode von T
Welche Mittel, solange Sie overide Equals Sie sind in Ordnung.
Der Grund, Sie sind Code funktioniert nicht, weil Sie Vorname überprüfen == Nachname.
finden Sie unter https://msdn.microsoft.com/library/ bb348436 (v = VS.100) aspx und https://msdn.microsoft.com/en-us/library/ms224763 (v = VS.100) aspx