• Blog
  • Info Support
  • Career
  • Training
  • International Group
  • Info Support
  • Blog
  • Career
  • Training
  • International Group
  • Search
logo InfoSupport
  • Latest blogs
  • Popular blogs
  • Experts
      • Alles
      • Bloggers
      • Speakers
  • Meet us
  • About us
    • nl
    • en
    • .NET
    • Advanced Analytics
    • Agile
    • Akka
    • Alexa
    • Algorithms
    • Api's
    • Architectuur
    • Artificial Intelligence
    • ATDD
    • Augmented Reality
    • AWS
    • Azure
    • Big Data
    • Blockchain
    • Business Intelligence
    • Cloud
    • Code Combat
    • Cognitive Services
    • Communicatie
    • Containers
    • Continuous Delivery
    • CQRS
    • Cyber Security
    • Dapr
    • Data
    • Data & Analystics
    • Data Science
    • Data Warehousing
    • Databricks
    • DevOps
    • Digital Days
    • Docker
    • eHealth
    • Enterprise Architecture
    • Hacking
    • Infrastructure & Hosting
    • Innovatie
    • Integration
    • Internet of Things
    • Java
    • Machine Learning
    • Microservices
    • Microsoft
    • Microsoft Bot Framework
    • Microsoft Data Platform
    • Mobile Development
    • Mutation Testing
    • Open source
    • Pepper
    • Power BI
    • Privacy & Ethiek
    • Python
    • Quality Assistance & Test
    • Quality Assurance & Test
    • Requirements Management
    • Scala
    • Scratch
    • Security
    • SharePoint
    • Software Architecture
    • Software development
    • Software Factory
    • SQL Server
    • SSL
    • Start-up
    • Startup thinking
    • Stryker
    • Test Quality
    • Testing
    • TLS
    • TypeScript
    • Various
    • Web Development
    • Web-scale IT
    • Xamarin
    • Alles
    • Bloggers
    • Speakers
Home » The missing link: The LinqEqualityComparer
  • The missing link: The LinqEqualityComparer

    • By Manuel Riezebosch
    • .NET 11 years ago
    • .NET 0 comments
    • .NET .NET
    The missing link: The LinqEqualityComparer

    There are a few Linq extension methods that only support the combination of the Equals and GetHashCode methods of an object or the use of a custom EqualityComparer. When you want to select all items from a list that are not in a second list for example.

    Look at the following code:

    class MyClass
    {
        public string MyProperty { get; set; }
    }
    var l1 = new List<MyClass>
    {
        new MyClass { MyProperty = "shared" },
        new MyClass { MyProperty = "Item in l1" }
    };
    
    var l2 = new List<MyClass>
    {
        new MyClass { MyProperty = "shared" },
        new MyClass { MyProperty = "Item in l2" }
    };

    When you are combining two lists based on the value of MyProperty, you have 3 options:

    1. Implement the Equals and GetHashCode method of the object, but that might not be very useful when the scenario is not very common;
    2. Create a custom EqualityComparer, which is an opportunity when MyClass is not in control;
    3. Don’t use the Except method but a use combination of other Linq extension methods instead:
    l1.Where(m1 => !l2.Any(m2 => m1.MyProperty == m2.MyProperty));

    I think this last option is used most, but it might have a performance penalty because of the lookup in l2. This might be reduced by creating a lookup when performance is important, but now I’ve also created a 4th option: the LinqEqualityComparer. It is a very simple class that is compares two objects based on an expression that is provided via the constructor. You will also have to make up a hash code. The GetHashCode is used by the runtime to do a quick pre-comparison before calling the more “expensive” Equals method. It is also possible to return a constant value if you cannot come up with a decent hash code. This ensures that the Equals method always gets called.

    public class LinqEqualityComparer<T> : EqualityComparer<T>
    {
        Func<T, T, bool> _equals;
        Func<T, int> _hash;
    
        public LinqEqualityComparer(Func<T, T, bool> equals, Func<T, int> hash)
        {
            _equals = equals;
            _hash = hash;
        }
    
        public override bool Equals(T x, T y)
        {
            return _equals(x, y);
        }
    
        public override int GetHashCode(T obj)
        {
            return _hash(obj);
        }
    }

    Now we can use the Except and Distinct methods with our new equality comparer:

    var comparer = new LinqEqualityComparer<MyClass>((x, y) => x.MyProperty == y.MyProperty, m => m.MyProperty.GetHashCode());
    var result = l1.Except(l2, comparer);

    Remark: It is very important that the correct hash code is used, because when using the hash code method of MyClass all objects will be unequal to the comparer!

    The next step is to create a factory class that creates a comparer and deduce the equals and hash code methods from a selected property. I also added an extra overload that takes an IEqualityComparer as input. This makes it possible to plug in an extra comparator for the actual comparison, for example the StringComparer.

    static class LinqEqualityComparer
    {
        public static LinqEqualityComparer<T> Create<T, TKey>(Func<T, TKey> keySelector)
        {
            // Use the Equals and the GetHashCode method from the type that is selected with the key selector.
            return new LinqEqualityComparer<T>((x, y) => keySelector(x).Equals(keySelector(y)), m => keySelector(m).GetHashCode());
        }
    
        public static LinqEqualityComparer<T> Create<T, TKey>(Func<T, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            // Use the Equals and the GetHashCode method from the comparer using the types that from the key selector as input.
            return new LinqEqualityComparer<T>((x, y) => comparer.Equals(keySelector(x), keySelector(y)), m => comparer.GetHashCode(keySelector(m)));
        }
    }

    This is how it looks in action:

    var comparer = LinqEqualityComparer.Create<MyClass, string>(m => m.MyProperty);
    var result = l1.Except(l2, comparer);

    and the extra overload:

    var comparer = LinqEqualityComparer.Create<MyClass, string>(m => m.MyProperty, StringComparer.OrdinalIgnoreCase);
    var result = l1.Except(l2, comparer);

    The last step is an extension method class to extend the Linq extensions methods. These methods will only reroute the call to the original Linq methods while plugging in the new equality comparer. I only implemented the Except method, but the Distinct and Intersect are not that different.

    public static class LinqExtensions
    {
        public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, TKey> keySelector)
        {
            return first.Except(second, LinqEqualityComparer.Create(keySelector));
        }
    
        public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            return first.Except(second, LinqEqualityComparer.Create(keySelector, comparer));
        }
    }
     

    The first method can be used like this:

    var result = l1.Except(l2, m => m.MyProperty);

    And the second method like this, where the casing of the selected property is ignored:

    var result = l1.Except(l2, m => m.MyProperty, StringComparer.OrdinalIgnoreCase);

     

    I don’t now if this new comparison class will be very helpful. It turned out that in the situation where I discovered this method that I wasn’t interested in the whole object but only in the specific property where I was filtering on. So I found out there was a 5th method to solve this issue that might be more elegant and less work in the first place:

    var result = l1.Select(m => m.MyProperty).Except(l2.Select(m => m.MyProperty));

     

    But I can imagine that there are other situations where an EqualityComparer must be used  and the LinqEqualityComparer might help… At last it was a nice exercise for me 🙂

    Share this

Manuel Riezebosch

View profile

Related IT training

Go to training website

Related Consultancy solutions

Go to infosupport.com

Related blogs

  • Innovative patterns in software quality management

    Innovative patterns in software quality management Tom van den Berg - 1 month ago

  • Developing a Service Fabric service using another host

    Developing a Service Fabric service using another host Tim van de Lockand - 5 months ago

  • Adding a package to your private WinGet.RestSource feed…

    Adding a package to your private WinGet.RestSource feed… Léon Bouquiet - 10 months ago

Related downloads

  • Beslisboom voor een rechtmatig ‘kopietje productie’

  • Klantreferentie: Remmicom zet wetgeving om in intellige…

  • Klantreferentie RDW: Samenwerken voor veilig en vertrou…

  • Klantreferentie BeFrank: Strategische IT voor een innov…

  • Wie durft te experimenteren met data in de zorg?

Related videos

  • mijnverzekeringenopeenrij.nl

    mijnverzekeringenopeenrij.nl

  • Winnaar | Innovation Projects 2017

    Winnaar | Innovation Projects 2017

  • Explore | Info Support & HAN & Poliskluis

    Explore | Info Support & HAN & Poliskluis

  • LifeApps bij HagaZiekenhuis

    LifeApps bij HagaZiekenhuis

  • Info Support | Bedrijfsfilm

    Info Support | Bedrijfsfilm

Nieuwsbrief

* verplichte velden

Contact

  • Head office NL
  • Kruisboog 42
  • 3905 TG Veenendaal
  • T +31 318 552020
  • Call
  • Mail
  • Directions
  • Head office BE
  • Generaal De Wittelaan 17
  • bus 30 2800 Mechelen
  • T +32 15 286370
  • Call
  • Mail
  • Directions

Follow us

  • Twitter
  • Facebook
  • Linkedin
  • Youtube

Newsletter

Sign in

Extra

  • Media Library
  • Disclaimer
  • Algemene voorwaarden
  • ISHBS Webmail
  • Extranet
Beheer cookie toestemming
Deze website maakt gebruik van Functionele en Analytische cookies voor website optimalisatie en statistieken.
Functioneel
Altijd actief
De technische opslag of toegang is strikt noodzakelijk voor het legitieme doel het gebruik mogelijk te maken van een specifieke dienst waarom de abonnee of gebruiker uitdrukkelijk heeft gevraagd, of met als enig doel de uitvoering van de transmissie van een communicatie over een elektronisch communicatienetwerk.
Voorkeuren
De technische opslag of toegang is noodzakelijk voor het legitieme doel voorkeuren op te slaan die niet door de abonnee of gebruiker zijn aangevraagd.
Statistieken
De technische opslag of toegang die uitsluitend voor statistische doeleinden wordt gebruikt. De technische opslag of toegang die uitsluitend wordt gebruikt voor anonieme statistische doeleinden. Zonder dagvaarding, vrijwillige naleving door uw Internet Service Provider, of aanvullende gegevens van een derde partij, kan informatie die alleen voor dit doel wordt opgeslagen of opgehaald gewoonlijk niet worden gebruikt om je te identificeren.
Marketing
De technische opslag of toegang is nodig om gebruikersprofielen op te stellen voor het verzenden van reclame, of om de gebruiker op een website of over verschillende websites te volgen voor soortgelijke marketingdoeleinden.
Beheer opties Beheer diensten Beheer leveranciers Lees meer over deze doeleinden
Voorkeuren
{title} {title} {title}