62

I have a strongly typed list of custom objects, MyObject, which has a property Id, along with some other properties.

Let's say that the Id of a MyObject defines it as unique and I want to check if my collection doesn't already have a MyObject object that has an Id of 1 before I add my new MyObject to the collection.

I want to use if(!List<MyObject>.Contains(myObj)), but how do I enforce the fact that only one or two properties of MyObject define it as unique?

I can use IComparable? Or do I only have to override an Equals method? If so, I'd need to inherit something first, is that right?

CarenRose
  • 1,266
  • 1
  • 12
  • 24
topwik
  • 3,487
  • 8
  • 41
  • 65

6 Answers6

71

List<T>.Contains uses EqualityComparer<T>.Default, which in turn uses IEquatable<T> if the type implements it, or object.Equals otherwise.

You could just implement IEquatable<T> but it's a good idea to override object.Equals if you do so, and a very good idea to override GetHashCode() if you do that:

public class SomeIDdClass : IEquatable<SomeIDdClass>
{
    private readonly int _id;
    public SomeIDdClass(int id)
    {
        _id = id;
    }
    public int Id
    {
        get { return _id; }
    }
    public bool Equals(SomeIDdClass other)
    {
        return null != other && _id == other._id;
    }
    public override bool Equals(object obj)
    {
        return Equals(obj as SomeIDdClass);
    }
    public override int GetHashCode()
    {
        return _id;
    }
}

Note that the hash code relates to the criteria for equality. This is vital.

This also makes it applicable for any other case where equality, as defined by having the same ID, is useful. If you have a one-of requirement to check if a list has such an object, then I'd probably suggest just doing:

return someList.Any(item => item.Id == cmpItem.Id);
Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
29

List<T> uses the comparer returned by EqualityComparer<T>.Default and according to the documentation for that:

The Default property checks whether type T implements the System.IEquatable(Of T) interface and, if so, returns an EqualityComparer(Of T) that uses that implementation. Otherwise, it returns an EqualityComparer(Of T) that uses the overrides of Object.Equals and Object.GetHashCode provided by T.

So you can either implement IEquatable<T> on your custom class, or override the Equals (and GetHashCode) methods to do the comparison by the properties you require. Alternatively you could use linq:

bool contains = list.Any(i => i.Id == obj.Id);
Lee
  • 142,018
  • 20
  • 234
  • 287
  • I would go with the Linq statement. If you wanted, you could implement an extension method Contains(this List list, T item, Func comparer) that would look like List.Contains but add the ability to provide a comparison delegate. It would wrap a Linq statement similar to what's provided here. – KeithS Sep 03 '10 at 17:49
  • 1
    +1 for the LINQ too, because it allows you to compare classes from external sources. And for code elegance – PPC Oct 19 '12 at 00:59
9

You can use LINQ to do this pretty easily.

var result = MyCollection.Any(p=>p.myId == Id);
if(result)
{
     //something
}
starwed
  • 2,536
  • 2
  • 25
  • 39
Robaticus
  • 22,857
  • 5
  • 54
  • 63
2

You can override Equals and GetHashCode, implement an IEqualityComparer<MyObject> and use that in the Contains call, or use an extension method like Any

if (!myList.Any(obj => obj.Property == obj2.Property && obj.Property2 == obj2.Property2))
   myList.Add(obj2);
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
  • +1, this is a good high-performance solution, especially if you use sets or dictionaries. (speaking to the Equals/GetHashCode point) – Kirk Woll Sep 03 '10 at 17:25
1

First define helper class with IEqualityComparer.

public class MyEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, int> _hashDelegate;

    public MyEqualityComparer(Func<T, int> hashDelegate)
    {
        _hashDelegate = hashDelegate;
    }

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

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

Then in your code, just define comparator and use it:

var myComparer = new MyEqualityComparer<MyObject>(delegate(MyObject obj){
    return obj.ID;
});

var result = collection
   .Where(f => anotherCollection.Contains(f.First, myComparer))
   .ToArray();

This way you can define the way how Equality is computed without modifying your classes. You can also use it for processing object from third party libraries as you cannot modify their code.

Tomas Kubes
  • 23,880
  • 18
  • 111
  • 148
0

You can use IEquatable<T>. Implement this in your class, and then check to see if the T passed to the Equals has the same Id as this.Id. I'm sure this works for checking a key in a dictionary, but I've not used it for a collection.

davisoa
  • 5,407
  • 1
  • 28
  • 34