Distinct が LINQ to Objects で機能しない
-
21-09-2019 - |
質問
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;
}
}
これは、「LINQ in Action」の例に基づいています。リスト 4.16。
これにより、Jon Skeet が 2 回出力されます。なぜ?Author クラスの Equals メソッドをオーバーライドしてみました。まだ Distinct は機能していないようです。私には何が欠けているのでしょうか?
編集:== および != 演算子のオーバーロードも追加しました。まだ助けがありません。
public static bool operator ==(Author a, Author b)
{
return true;
}
public static bool operator !=(Author a, Author b)
{
return false;
}
解決
それがカスタムオブジェクトに来るとき明確なLINQは、そのスマートではありません。
それがないすべてはあなたのリストを見て、それが(それは彼らがメンバーフィールドに同じ値を持っていることを気にしない)2つの異なるオブジェクトを持っていることがわかります。
一つの回避策は、こちら 示すようIEquatableインターフェイスを実装することです。
あなたはそれが動作するはずですので、のようなあなたの著者クラスを変更する場合。
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;
}
}
としてそれを試してみてください 他のヒント
の Distinct()
メソッドは、参照型の参照の等価性をチェックします。これは、同じ値を含む異なるオブジェクトではなく、文字通り同じ重複したオブジェクトを探していることを意味します。
があります 過負荷 それは IEqualityComparer, そのため、特定のオブジェクトが別のオブジェクトと等しいかどうかを判断するための異なるロジックを指定できます。
Author を通常のオブジェクトのように動作させたい場合 (つまり、等価性の参照のみ)、ただし、名前の値による個別の等価性チェックの目的には、 IEqualityComparer. 。常に Author オブジェクトを名前の値に基づいて比較したい場合は、次のようにします。 GetHashCode と Equals をオーバーライドする, 、 または IEquatable を実装する.
メンバーの二人は、 IEqualityComparer
インターフェースは Equals
そして GetHashCode
. 。2 つであるかどうかを判断するためのロジック Author
オブジェクトが等しいとは、名と姓の文字列が同じであるように見えます。
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();
}
}
IEquatable
を実施せずに別の溶液、Equals
とGetHashCode
はLINQs GroupBy
メソッドを使用するとIGroupingから最初の項目を選択することである。
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);
}
ユーザー定義のデータ型のリストから異なる値を取得するもう一つの方法があります:
YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();
確かに、それはデータの明確なセットを提供します。
Distinct()
列挙可能なオブジェクトに対してデフォルトの等価比較を実行します。オーバーライドしていない場合 Equals()
そして GetHashCode()
, の場合、デフォルトの実装が使用されます。 object
, 、参照を比較します。
簡単な解決策は、 正しい の実装 Equals()
そして GetHashCode()
比較しているオブジェクト グラフに参加するすべてのクラス (つまり、Book と Author)。
の IEqualityComparer
インターフェイスは実装できるようにするための便利なものです Equals()
そして GetHashCode()
比較する必要があるクラスの内部にアクセスできない場合、または別の比較方法を使用している場合は、別のクラスで。
あなたはオーバーライドされた等号を()しましたが、あなたはまた、GetHashCodeメソッドをオーバーライドすることを確認してください()
上記の答えは間違っています!!!MSDN で述べられているように、Distinct はデフォルトの Equator を返します。 Default プロパティは、型 T が System.IEquatable インターフェイスを実装しているかどうかを確認し、実装している場合は、その実装を使用する EqualityComparer を返します。 それ以外の場合は、T によって提供される Object.Equals および Object.GetHashCode のオーバーライドを使用する EqualityComparer を返します。
つまり、Equals をオーバーライドする限り問題はありません。
コードが機能しない理由は、firstname==lastname をチェックしているためです。
見る https://msdn.microsoft.com/library/bb348436(v=vs.100).aspx そして https://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx