Hey, Why Isn’t My LINQ Query Working?

I wanted to share something that came up at work last week.  One of our developers was writing some LINQ and was getting some results back that seemed strange.  I thought I’d share what was going on in the hopes that someone else might benefit from it.

Let’s start by assuming you have a business object you’re working with.  In this example we’ll use something really simple, like this

public class SimpleKeyValue{
   public string Key {get; set;}
   public string Value {get; set;}
}

Now let’s say that we have a collection of those objects and we want to use some LINQ functionality to do some LINQy magic.  For example, let’s say that I have a function where I get a list of some color codes and their associated names stored in my SimpleKeyValue object.  Then, I want to see whether or not the color Red is in that list.  The code for that might look something like this.


class Program
{
    static void Main(string[] args)
    {
       var containsRed = GetRainbowColorCodes()
          .Contains(new SimpleKeyValue { Key = "Red", Value = "#FF0000" });
       Console.WriteLine(String.Format("Contains returns {0}", containsRed));
       Console.ReadLine();
    }

    private static IEnumerable<SimpleKeyValue> GetRainbowColorCodes()
    {
       return new List<SimpleKeyValue>
       {
          new SimpleKeyValue {Key = "Red", Value = "#FF0000"},
          new SimpleKeyValue {Key = "Orange", Value = "#FF8040"},
          new SimpleKeyValue {Key = "Yellow", Value = "#FFFF00"},
          new SimpleKeyValue {Key = "Green", Value = "#254117"},
          new SimpleKeyValue {Key = "Blue", Value = "#0000FF"},
          new SimpleKeyValue {Key = "Indigo", Value = "#800080"},
          new SimpleKeyValue {Key = "Violet",Value = "#8D38C9"}
       };
    }
}

The containsRed variable is going to be true, right?  Well, you might think so, but unfortunately no, it’s not.  So what’s going on here?

The issue here is that many LINQ functions, like Contains(), Except() or Intersect() are performing object comparison and call Equals() on those objects as the collection is iterated.  The thing to keep in mind is that, by default, Equals() on objects means reference equality.  That means that Equals is checking to see if the two objects being compared refer to the same memory address.  In my example above, the object in my collection of rainbow colors does not reside at the same memory location as the object being passed into the Contains method.  Because of this, the call to Equals() returns false and Contains lets you know that it didn’t find the item in the collection.

There are several ways to fix this by making sure when we are doing this kind of comparison that instead of Equals checking to see that the two objects point to the same location in memory it checks that the data contained in the objects are equivalent.  This is called Value comparison.

One simple way to do this is to make the SimpleKeyValue class a struct instead of a class.  The default behavior of Equals() for structs is to use reflection to compare the values of all the fields contained within the struct.  By simply changing the word class in our declaration of SimpleKeyValue to struct, the code now behaves as we would expect it to.

public struct SimpleKeyValue{
   public string Key {get; set;}
   public string Value {get; set;}
}

So what if you can’t change your class to a struct?  Are you just out of luck?  No, of course not.  In this case, we can override the Equals method in our SimpleKeyValue class (p.s. if you override Equals, make sure you also override GetHashCode, more about that in a later post).  If we do that, our class now looks like this.

public class SimpleKeyValue
{
    public string Key { get; set; }
    public string Value { get; set; }

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

       return (this.Key.Equals(other.Key) && this.Value.Equals(other.Value));
     } 
   
    public override int GetHashCode()
    {
       return String.Format("{0}{1}", this.Key, this.Value).GetHashCode();
    }
}

Now that we’ve done this, the Contains method uses our overridden Equals method to determine whether or not the object passed to it is in our collection.  Because we’re now explicitly telling it how to determine if these objects are equal, the LINQ statement now returns true as we expect it to.

Please note!  My implementation of GetHashCode in that class is not very good, but it was quick and easy for the purposes of this post.  If you’re doing this yourself, you probably want to read up on some better GetHashCode implementations.  Also, if this was my production code, I would probably not override Equals in this business object, but more on THAT topic in the next post.

One thought on “Hey, Why Isn’t My LINQ Query Working?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s