Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the correct way to implement .Distinct() on a List<[linq_custom_object]>()?

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.

like image 809
LaRae White Avatar asked Feb 14 '23 12:02

LaRae White


2 Answers

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);
    }
}
like image 92
Lasse V. Karlsen Avatar answered Feb 16 '23 02:02

Lasse V. Karlsen


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
like image 22
decPL Avatar answered Feb 16 '23 01:02

decPL