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 两次。为什么?我什至尝试在 Author 类中覆盖 Equals 方法。Still 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 在自定义对象方面并不那么聪明。

它所做的只是查看您的列表并查看它有两个不同的对象(它不关心它们对于成员字段具有相同的值)。

一种解决方法是实现 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界面上的两个成员是EqualsGetHashCode。您确定两个Author对象是否相等的逻辑似乎是 First name 和 Last name 字符串是否相同。

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

另一个没有实现的解决方案IEquatable是使用 LINQs方法Equals并从 IGrouping 中选择第一项。GetHashCodeGroupBy

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

还有另一种方法可以从用户定义的数据类型列表中获取不同的值:

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

当然,它将提供不同的数据集

于 2016-08-25T08:19:35.190 回答
25

Distinct()对 enumerable 中的对象执行默认相等比较。如果您没有覆盖Equals()and GetHashCode(),那么它使用默认实现 on object,它比较引用。

简单的解决方案是为所有参与您正在比较的对象图(即书籍和作者)的类添加正确的实现。Equals()GetHashCode()

IEqualityComparer接口很方便,当您无法访问需要比较的类的内部或使用不同的比较方法时,该接口允许您在单独的类中实现Equals()和实现。GetHashCode()

于 2009-09-02T04:14:54.670 回答
13

您已经覆盖了 Equals(),但请确保您还覆盖了 GetHashCode()

于 2009-09-02T04:01:12.890 回答
10

楼上的回答错了!!!如 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).aspxhttps://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx

于 2016-02-03T16:36:08.453 回答
2

您可以通过以下几种方式实现:

1. 您可以实现 IEquatable 接口,如图所示Enumerable.Distinct 方法,或者您可以在这篇文章中查看@skalb 的答案

2.如果您的对象没有唯一键,您可以使用 GroupBy 方法来获得不同的对象列表,您必须对对象的所有属性进行分组,然后选择第一个对象。

例如如下所示并为我工作:

var distinctList= list.GroupBy(x => new {
                            Name= x.Name,
                            Phone= x.Phone,
                            Email= x.Email,
                            Country= x.Country
                        }, y=> y)
                       .Select(x => x.First())
                       .ToList()

MyObject 类如下所示:

public class MyClass{
       public string Name{get;set;}
       public string Phone{get;set;}
       public string Email{get;set;}
       public string Country{get;set;}
}

3.如果你的对象有唯一的key,你只能在group by中使用。

例如我的对象的唯一键是 Id。

var distinctList= list.GroupBy(x =>x.Id)
                      .Select(x => x.First())
                      .ToList()
于 2020-06-11T15:33:14.573 回答
1

您可以在列表上使用扩展方法,该方法根据计算的哈希检查唯一性。您还可以更改扩展方法以支持 IEnumerable。

例子:

public class Employee{
public string Name{get;set;}
public int Age{get;set;}
}

List<Employee> employees = new List<Employee>();
employees.Add(new Employee{Name="XYZ", Age=30});
employees.Add(new Employee{Name="XYZ", Age=30});

employees = employees.Unique(); //Gives list which contains unique objects. 

扩展方法:

    public static class LinqExtension
        {
            public static List<T> Unique<T>(this List<T> input)
            {
                HashSet<string> uniqueHashes = new HashSet<string>();
                List<T> uniqueItems = new List<T>();

                input.ForEach(x =>
                {
                    string hashCode = ComputeHash(x);

                    if (uniqueHashes.Contains(hashCode))
                    {
                        return;
                    }

                    uniqueHashes.Add(hashCode);
                    uniqueItems.Add(x);
                });

                return uniqueItems;
            }

            private static string ComputeHash<T>(T entity)
            {
                System.Security.Cryptography.SHA1CryptoServiceProvider sh = new System.Security.Cryptography.SHA1CryptoServiceProvider();
                string input = JsonConvert.SerializeObject(entity);

                byte[] originalBytes = ASCIIEncoding.Default.GetBytes(input);
                byte[] encodedBytes = sh.ComputeHash(originalBytes);

                return BitConverter.ToString(encodedBytes).Replace("-", "");
            }
于 2020-06-03T10:32:25.047 回答
1

下面代码中的 Equal 运算符不正确。

老的

public bool Equals(Author other)
{
    if (FirstName == other.FirstName && LastName == other.LastName)
        return true;

    return false;
}

新的

public override bool Equals(Object obj)
{
    var other = obj as Author;

    if (other is null)
    {
        return false;
    }

    if (FirstName == other.FirstName && LastName == other.LastName)
        return true;

    return false;
}
于 2021-07-12T06:46:35.380 回答