【问题标题】:Distinct not working with LINQ to Objects不同的是不使用 LINQ to Objects
【发布时间】:2009-09-02 03:50:16
【问题描述】:
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;
    }

【问题讨论】:

    标签: c# .net linq iequatable iequalitycomparer


    【解决方案1】:

    LINQ Distinct 在自定义对象方面并不那么聪明。

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

    一种解决方法是实现 IEquatable 接口,如 here 所示。

    如果您像这样修改 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;
        }
    }
    

    Try it as DotNetFiddle

    【讨论】:

    • IEquatable 很好但不完整;您应该始终同时实现 Object.Equals() 和 Object.GetHashCode(); IEquatable.Equals 不会覆盖 Object.Equals,因此在进行非强类型比较时会失败,这通常发生在框架中并且总是在非泛型集合中。
    • 那么使用 Rex M 建议的采用 IEqualityComparer 的 Distinct 覆盖会更好吗?我的意思是,如果我不想落入陷阱,我应该怎么做。
    • @Tanmoy 这取决于。如果您希望 Author 通常表现得像一个普通对象(即仅引用相等性)但为了区分的目的检查名称值,请使用 IEqualityComparer。如果您总是希望根据名称值比较 Author 对象,则覆盖 GetHashCode 和 Equals,或实现 IEquatable。
    • 我实现了IEquatable(并覆盖了Equals/GetHashCode),但我的断点都没有在Linq Distinct上的这些方法中触发?
    • @PeterX 我也注意到了这一点。我在GetHashCodeEquals 中有断点,它们在foreach 循环中被击中。这是因为var temp = books.SelectMany(book =&gt; book.Authors).Distinct();返回了IEnumerable,意思是请求不会马上执行,只有在数据使用时才会执行。如果您想立即获得此触发的示例,请在.Distinct() 之后添加.ToList(),您将在foreach 之前看到EqualsGetHashCode 中的断点。
    【解决方案2】:

    Distinct() 方法检查引用类型的引用相等性。这意味着它实际上是在寻找重复的相同对象,而不是包含相同值的不同对象。

    有一个overload 接受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();
        }
    }
    

    【讨论】:

    • 谢谢!您的 GetHashCode() 实现向我展示了我仍然缺少的东西。我返回的是{传入的对象}.GetHashCode(),而不是{用于比较的属性}.GetHashCode()。这造成了差异并解释了为什么我的仍然失败 - 两个不同的引用将有两个不同的哈希码。
    【解决方案3】:

    另一个不实现IEquatableEqualsGetHashCode 的解决方案是使用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);
    }
    

    【讨论】:

    • 它确实对我有所帮助,仅考虑性能,它的执行速度是否与上述方法相同?
    • 比实现方法复杂化要好得多,如果使用 EF 会将工作委托给 sql server。
    • 虽然这种方法可能有效,但由于被分组的东西的数量会出现性能问题
    • @Bellash 让它工作,然后让它变得更快。当然,这种分组可能会导致更多的工作要做。但有时实现比你想要的更多是很麻烦的。
    • 我更喜欢这个解决方案,但随后在 groupby 中使用“新”对象:.GroupBy(y =&gt; new { y.FirstName, y.LastName })
    【解决方案4】:

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

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

    当然,它会给出不同的数据集

    【讨论】:

    • VB: .GroupBy(Function(d) New With {Key d.field1, Key d.field2, Key d.field3}).Select(Function(d) d.FirstOrDefault()).OrderBy(Function(X) X.dield1).ToList()
    【解决方案5】:

    Distinct() 对 enumerable 中的对象执行默认相等比较。如果您尚未覆盖 Equals()GetHashCode(),则它使用 object 上的默认实现,用于比较引用。

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

    IEqualityComparer 接口很方便,当您无法访问需要比较的类的内部时,或者如果您正在使用不同的比较方法。

    【讨论】:

    • 非常感谢您对参与对象的精彩评论。
    【解决方案6】:

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

    【讨论】:

    • +1 用于强调 GetHashCode()。不要像&lt;custom&gt;^base.GetHashCode() 那样添加基本的 HashCode 实现
    【解决方案7】:

    以上回答错误!!! 与 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

    【讨论】:

      【解决方案8】:

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

      1.你可以实现IEquatable接口,如图Enumerable.Distinct Method或者你可以看到@skalb's answer at this post

      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()
      

      【讨论】:

        【解决方案9】:

        您可以在列表上使用扩展方法,该方法根据计算的哈希检查唯一性。 您还可以更改扩展方法以支持 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("-", "");
                    }
        

        【讨论】:

          【解决方案10】:

          下面代码中的 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;
          }
          

          【讨论】:

            猜你喜欢
            • 2019-03-20
            • 1970-01-01
            • 1970-01-01
            • 2010-11-27
            • 1970-01-01
            • 1970-01-01
            • 2018-03-03
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多