LINQ: The overloaded Contains Operator

A LINQ Tutorial

As part of my continuing (and occasional) series of mini-tutorials on LINQ this posting will consider the overloaded Contains operator. This operator allows you to check whether a list contains a given value, as illustrated in the following code that you can drop into LinqPad or wrap within a console application:

var someList = Enumerable.Range(1,10);

Console.WriteLine(
 "someList contains 6? {0}, someList contains 12? {1}",
 someList.Contains(6), someList.Contains(12));

 

The output for this is

someList contains 6? True, someList contains 12? False

As noted, the Contains operator is overloaded, and you are free to pass in your own comparison method to facilitate comparing items of different types.

To see this at work, create a Console Application in Visual Studio. Within the application create a Person class and derived from that, a Student class:

class Person
{
    public int ID { get; set; }
    public string FullName { get; set; }
}

class Student : Person
{
    public string Major { get; set; }
}

 

Next, create a class derived from IEqualityComparer, which will establish whether two People are equal or not using whatever you choose as the criteria. You might match FullName fields or, more likely, ID:

class StudentToPersonEquals : IEqualityComparer<Person>
 {
     public bool Equals( Person x, Person y )
     {
         if (x.ID == y.ID)
             return true;
         else
             return false;
     }

     public int GetHashCode( Person obj )
     {
         return obj.GetHashCode();
     }
 }

 

With this in place, you can instantiate a number of Person and Student objects, and place them in a collection:

var people = new List<Person>();

var PaulBetts = new Student() 
{ 
    FullName =  "Paul Betts", 
    ID = 1, 
    Major = "Computer Science" 
};
people.Add(PaulBetts);

people.Add( new Person() 
    { ID = 2, FullName = "Jesse Liberty" } );

people.Add( new Student() 
    { 
        ID = 3, 
        FullName = "George Washington", 
        Major = "Favorite on Fawlty Towers" 
    } );

people.Add( new Student() 
    { 
        ID = 4, 
        FullName = "John Adams", 
        Major = "History" 
    } );


 

You can now use an instance of your StudentToPersonEquals class as the comparer for the Contains operator,

Console.WriteLine ("People contains Paul Betts? {0}",
    people.Contains(PaulBetts, new StudentToPersonEquals() ));

 

This allows you to pass in an instance of the Student class and have it identified by the Contains operator based on the criteria specified in your comparison class.

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Data, Linq, Mini-Tutorial and tagged . Bookmark the permalink.

9 Responses to LINQ: The overloaded Contains Operator

  1. Danila George says:

    Another way of using the overloaded Contains extension method would be to pass a GenericEqualityComparer like this:

    public class GenericEqualityComparer : IEqualityComparer
    {
    private Func equalityFunction;

    public GenericEqualityComparer(Func equalityFunction)
    {
    this.equalityFunction = equalityFunction;
    }

    public bool Equals(T x, T y)
    {
    return equalityFunction(x,y);
    }

    public int GetHashCode(T obj)
    {
    return obj.GetHashCode();
    }
    }

    Given your example you can easily use this comparer for any given type T, without having to write a comparer implementation for each class, like so:

    var personEqualityComparer = new GenericEqualityComparer((x,y) => x.ID == y.ID);
    var contains = people.Contains(personImLookingFor, personEqualityComparer);
    var foundString = contains ? “found” : “not found”;
    Console.WriteLine(string.Format(“{0} was {1}”, foundString));

  2. Bryan Watts says:

    I understand your struggle to demonstrate a feature like this with sufficient clarity while not making it overly simplistic. That overload is tough to create an example for which wouldn’t be better addressed by .Any().

    As a corollary, you could also point out that there are ready-made comparers in the framework, such as those on the StringComparer class. I understand you want to show an implementation of IEqualityComparer, but it doesn’t necessarily drive home why reusable comparers are valuable. “.Any(name => String.Equals(name, person.Name, StringComparison.InvariantCultureIgnoreCase))” is quite wordy and brittle if you use it everywhere.

    Also, GetHashCode() should take into account the same rules for identity as Equals(), which implies “return obj.ID.GetHashCode();” instead of “return obj.GetHashCode();”. I know you aren’t expecting .Contains() to use GetHashCode(), but you might want to call that out in order to avoid people creating incorrect comparers in other contexts.

    Thanks for the example.

  3. herzmeister der welten says:

    I’m not afraid of creating classes, actually I’m one of those types who rather tend towards over-engineering and not hearing the whisperings of YAGNI in my ear. 😉

    But it’s also true that we sometimes do have to foresee future aspects when building base libraries to save ourselves from changing stuff in 100s of places later. It really is a challenge sometimes to find the right balance.

  4. This is a handy overload and can be very useful for objects that cannot that easily identified by their ID.

    Anyway, Jesse, I am a little suprised by the way you implemented the Equals() method of your StudentToPersonEquals equality comparer. Why didn’t you implement it the following way:
    return x.ID == y.ID;

    Generally, the » return true; « and » else return false; « constructs are used by programming beginners having no further understanding of boolean values — which you, of course, do have. This is why I am all the more astonished seeing you writing the code that way …

    • When I write demo code, I generally write in a much more verbose form so that the logic of the code is crystal clear. I don’t see it as poor coding, just a bit verbose.

      That said, this one may have overdone it. 🙂

      • Martin says:

        Actually I thank that is the better way, because then you can more easily see what is happening, when debugging.
        (At least for more complex examples)

  5. herzmeister der welten says:

    If I’d have to create a class for every type of comparison I have to do in my software, I’d soon reach Microsoft’s spheres of bloat. :->

    So in any case I’d prefer

    bool contains = people.Any(cur => cur.ID == PaulBetts.ID);

    If a object is a Person or a Student is also pretty irrelevant here.

    • Correct on both points. But the trick with creating a demo is to create one that is sufficiently complex as to show the methodology without being so complex that it is difficult to understand. When and where you would use the comparison is very much a matter of the semantics of your program; it may well be that the client has two objects with no access to the comparable values (e.g., the ID is private) and must rely on the object types and the comparer to make the comparison.

    • Lee says:

      Why are devs afraid of creating classes? Creating a class for your comparers allows for compositional reuse throughout your program. Especially if you want to change how the comparison is made later, maybe later you want to compare name instead of ID. Do you want to change it in one place or 30 places? Good luck searching for it.

      The reasoning for using an anonymous delegate doesn’t make sense. Especially considering that the framework instantiates an object when calling an anonymous type.

      I love anonymous delegation, but there are times when creating and object is far better in terms of maintainability.

Comments are closed.