web-dev-qa-db-ja.com

LINQ to Objectsで明確に機能しない

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

LINQ Distinctは、カスタムオブジェクトに関してはそれほどスマートではありません。

リストを見て、2つの異なるオブジェクトがあることを確認するだけです(メンバーフィールドの値が同じであってもかまいません)。

回避策の1つは、 here のようにIEquatableインターフェイスを実装することです。

Authorクラスを次のように変更すると、動作するはずです。

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

DotNetFiddleとして試してみてください

143
skalb

Distinct()メソッドは、参照型の参照の等価性をチェックします。これは、同じ値を含む異なるオブジェクトではなく、文字通り複製された同じオブジェクトを探していることを意味します。

IEqualityComparer をとる オーバーロード があるため、特定のオブジェクトが別のオブジェクトと等しいかどうかを判断するためのさまざまなロジックを指定できます。

Authorを通常のオブジェクトのように通常動作させる(つまり、参照の等価性のみ)が、名前の値による明確なチェックの等価性を目的とする場合は、IEqualityComparerを使用します。名前の値に基づいてAuthorオブジェクトを常に比較する場合は、GetHashCodeとEqualsをオーバーライドするか、IEquatableを実装します。

IEqualityComparerインターフェイスの2つのメンバーは、EqualsGetHashCodeです。 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();
    }
}
63
Rex M

IEquatableEquals、および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);
}
42
Jehof

ユーザー定義のデータ型のリストから個別の値を取得する方法がもう1つあります。

YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();

確かに、それはデータの明確なセットを提供します

24
Ashu_90

Distinct()は、列挙可能なオブジェクトのデフォルトの等値比較を実行します。 Equals() および GetHashCode() をオーバーライドしていない場合、デフォルトの実装を使用しますobject、参照を比較します。

簡単な解決策は、correctの実装を追加することです Equals() および GetHashCode() 比較するオブジェクトグラフに参加するすべてのクラス(つまり、BookとAuthor)に。

IEqualityComparer インターフェースは、 Equals() および GetHashCode() 比較する必要のあるクラスの内部にアクセスできない場合、または別の比較方法を使用している場合は、別のクラスで。

20
AndyM

Equals()をオーバーライドしましたが、GetHashCode()もオーバーライドしてください。

10
Eric King

上記の答えは間違っています!!! MSDNに記載されているように明確な既定の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

7
Alex