【问题标题】:What's the correct way to implement .Distinct() on a List<[linq_custom_object]>()?在 List<[linq_custom_object]>() 上实现 .Distinct() 的正确方法是什么?
【发布时间】:2014-03-13 14:30:04
【问题描述】:

我有这个类DNS_Log,它有四个属性。我创建了这些对象的列表,我试图过滤这些对象,但仅针对不同的事件。 (在填充列表时,有很多重复)

这是正在填充的列表:

dnsLogs.Add( new DNS_Log { Destination = destination, 
                           Source_IP = sourceIp, 
                           Domain_Controller = domainController, 
                           DateTime = datetime });

这是我尝试仅过滤掉不同的尝试:

dnsLogs = dnsLogs.Distinct().ToList();

为什么这不起作用?我需要在不同的参数中使用一些 linq 表达式吗?我想比较对象作为一个整体的属性。有没有更简单的方法?

附:我已经尝试过制作一个似乎可以正常工作的自定义 IEqualityComparer&lt;DNS_Log&gt;,但我不知道如何在这种情况下实现它。

【问题讨论】:

  • 您将问题标记为 linq to sql,但看起来您正在手动创建对象列表。那么,是linq to sql,还是linq to objects?
  • 你说它不起作用是什么意思?它的实际作用是什么,与您的预期有何不同?
  • 你们是如何实现IEqualityComparer&lt;DNS_Log&gt;的?也许GetHashCode 的逻辑被破坏了。
  • Jim - 尝试过滤后返回相同数量的结果。
  • D Stanley - 哈希逻辑很好,我在 Distinct 参数中错误地实现了该类。

标签: c# .net linq distinct


【解决方案1】:

您有多种选择:

  1. DNS_Log 类型上实现IEquatable<T>
  2. 覆盖 Equals 和 GetHashCode 而不实现 IEquatable&lt;T&gt;
  3. 实现一个单独的IEqualityComparer<T> 并将其传递给 Distinct

注意!在下面的所有代码中,相等性检查假定== 运算符知道如何处理每种类型。这对于 DateTime 成员来说当然是正确的(假设它 is 也是 DateTime 类型),但我显然不能保证其他成员会起作用。如果 Destination 成员拥有一个尚未定义 == 运算符的类型,则这可能会做错事。由于您还没有发布自己的代码来实现这个比较器,所以不可能知道在这里做什么。

IEquatable&lt;T&gt;

public class DNS_Log : IEquatable<DNS_Log>
{

    public bool Equals(DNS_Log other)
    {
        if (other == null)
            return false;

        return (other.Destination == Destination
                && other.Source_IP == Source_IP
                && other.Domain_Controller == Domain_Controller
                && other.DateTime == DateTime);
    }

    public override int GetHashCode()
    {
        int hash = 23;
        hash = hash * 59 + (Destination == null ? 0 : Destination.GetHashCode());
        hash = hash * 59 + (Source_IP == null ? 0 : Source_IP.GetHashCode());
        hash = hash * 59 + (Domain_Controller == null ? 0 : Domain_Controller.GetHashCode());
        hash = hash * 59 + DateTime.GetHashCode();
        return hash;
    }
}

在没有接口的情况下覆盖 Equals 和 GetHashCode

public class DNS_Log
{

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        var other = obj as DNS_Log;
        if (other == null) return false;

        ... rest the same as above

单独的IEqualityComparer&lt;T&gt;

最后,您可以在调用Distinct时提供IEqualityComparer<T>

dnsLogs = dnsLogs.Distinct(new DNS_LogEqualityComparer()).ToList();

public class DNS_LogEqualityComparer : IEqualityComparer<DNS_Log>
{
    public int GetHashCode(DNS_Log obj)
    {
        int hash = 23;
        hash = hash * 59 + (obj.Destination == null ? 0 : obj.Destination.GetHashCode());
        hash = hash * 59 + (obj.Source_IP == null ? 0 : obj.Source_IP.GetHashCode());
        hash = hash * 59 + (obj.Domain_Controller == null ? 0 : obj.Domain_Controller.GetHashCode());
        hash = hash * 59 + obj.DateTime.GetHashCode();
        return hash;
    }

    public bool Equals(DNS_Log x, DNS_Log y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x == null) return false;

        return (x.Destination == y.Destination
            && x.Source_IP == y.Source_IP
            && .Domain_Controller == y.Domain_Controller
            && x.DateTime == y.DateTime);
    }
}

【讨论】:

    【解决方案2】:

    你基本上有三个选择:

    • 在调用Distinct 时提供您的自定义IEqualityComparer&lt;DNS_Log&gt;(类似于dnsLogs.Distinct(myEqualityComparerInstance).ToList();
    • DNS_Log实现IEquatable&lt;DNS_Log&gt;
    • DNS_Log 上覆盖EqualsGetHashCode

    【讨论】:

    • 谢谢,我将您的第一个建议用于我制作的自定义课程。我只是没有正确使用它,这实际上有点难过......
    【解决方案3】:

    为什么这不起作用?

    它不起作用,因为Distinct 使用GetHashCode + Equals 来比较对象。由于您没有从对象覆盖这些方法,因此您将获得仅比较引用的默认实现。

    所以你有几个选择:

    1. 您可以为Distinct 的重载实现自定义IEqualityComparer&lt;T&gt;
    2. 或在您的班级中覆盖Equals + GethashCode
    3. 另一种(效率较低)不需要创建新类或修改现有类的方法是使用匿名类型的内置GetHashCode+EqualsEnumerable.GroupBy

      IEnumerable<DNS_Log> distinctLogs = 
          from dns in dnsLogs
          group dns by  new { dns.Destination, dns.Source_IP,dns.Domain_Controller, dns.DateTime } into g 
          select g.First(); // change logic if you don't want an arbitrary object(first)
      

    这是第二种方法的示例:

    public class DNS_Log 
    {
        public string Destination{ get; set; }
        public int Source_IP { get; set; }
        public string Domain_Controller { get; set; }
        public DateTime DateTime { get; set; }
    
        public override bool Equals(object obj)
        {
            DNS_Log c2 = obj as DNS_Log;
            if (obj == null) return false;
            return Destination == c2.Destination && Source_IP == c2.Source_IP
                && Domain_Controller == c2.Domain_Controller && DateTime == c2.DateTime;
        }
    
        public override int GetHashCode()
        {
            unchecked 
            {
                int hash = 17;
                hash = hash * 23 + Destination.GetHashCode();
                hash = hash * 23 + Source_IP;
                hash = hash * 23 + Domain_Controller.GetHashCode();
                hash = hash * 23 + DateTime.GetHashCode();
                return hash;
            }
        }
    }
    

    IEqualityComparer 类(1. 方法)的 EqualsGethashCode 将是相似的。

    【讨论】:

      猜你喜欢
      • 2019-12-06
      • 2011-11-06
      • 2018-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多