Pregunta

I've got this class DNS_Log that has four properties. I've created a list of these objects that I am trying to filter though for only distinct occurrences. (When the list is being populated, there are a lot of repeats)

Here's the listing being populated:

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

Here's my attempt at trying to filter out the distinct ones only:

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

Why does this not work? Do I need some linq expression in the distinct param? I want to compare the the objects as a whole on their properties. Is there any easier way of doing this?

P.S. I've played around with making a custom IEqualityComparer<DNS_Log> that seems to work fine but I don't know how to implement it in this scenario.

¿Fue útil?

Solución 2

You basically have three options:

  • Provide your custom IEqualityComparer<DNS_Log> when calling Distinct (something like dnsLogs.Distinct(myEqualityComparerInstance).ToList();
  • Have DNS_Log implement IEquatable<DNS_Log>
  • Overwrite Equals and GetHashCode on DNS_Log

Otros consejos

You've got several options:

  1. Implement IEquatable<T> on the type DNS_Log
  2. Override Equals and GetHashCode without implementing IEquatable<T>
  3. Implement a separate IEqualityComparer<T> and pass it to Distinct

NOTE! In all the code below, the equality check assumes that the == operator knows how to deal with each type. That is certainly true for the DateTime member (assuming it is of type DateTime as well), but I obviously cannot guarantee that the others will work. If the Destination member holds a type for which the == operator has not been defined, this is likely to do the wrong thing. Since you haven't posted your own code for this comparer implementation, it's impossible to know what to do here.

IEquatable<T>

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

Overriding Equals and GetHashCode without the interface

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

Separate IEqualityComparer<T>

Lastly, you can provide an IEqualityComparer<T> when calling Distinct:

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

Why does this not work?

It does not work because Distinct uses GetHashCode + Equals to compare objects. Since you haven't overridden these methods from object you get the default implementation which just compares references.

So you have several options:

  1. You could either implement a custom IEqualityComparer<T> for the overload of Distinct
  2. or override Equals + GethashCode in your class
  3. Another (less efficient) approach which doesn't need to create a new class or modify an existing is to use the builtin GetHashCode+Equals of an anonymous type and Enumerable.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)
    

Here's an example of the second approach:

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

The Equals and GethashCode of the IEqualityComparer-class (1. approach) would be similar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top