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

10 に答える 10

188

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

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

回避策の 1 つは、ここに示すように 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 として試してください

于 2009-09-02T03:59:46.887 に答える
75

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

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

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

IEqualityComparerインターフェイスの 2 つのメンバーはEqualsGetHashCodeです。Author2 つのオブジェクトが等しいかどうかを判断するロジックは、姓と名の文字列が同じであると思われます。

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();
    }
}
于 2009-09-02T03:56:53.803 に答える
55

Another solution without implementing IEquatable, Equals and GetHashCode is to use the LINQs GroupBy method and to select the first item from the 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);
}
于 2012-07-11T14:35:30.757 に答える
47

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

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

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

于 2016-08-25T08:19:35.190 に答える
25

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

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

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

于 2009-09-02T04:14:54.670 に答える
13

Equals() をオーバーライドしましたが、GetHashCode() もオーバーライドしていることを確認してください

于 2009-09-02T04:01:12.890 に答える
10

上記の答えは間違っています!!! MSDN に記載されているように Distinct は、既定の Equator を返します。これは、既定のプロパティは、型 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を参照してください。

于 2016-02-03T16:36:08.453 に答える