Steve B
Steve B

Reputation: 37660

Correct way to implement a custom collection that rely on object subproperty?

Let's say I have a class like this :

public class MyClass {

    public int Id { get; set;}
    public string Name { get; set; }

}

Please note that this class, in my case, is out of control (from a 3rd party library) and does not implement equality comparers.

Now I want to have a collection of this items that allow me to remove items based on their index.

I'm expecting to be able to do :

void Foo()
{
    var collection = new MyClassCollection();

    var a = new MyClass { Id = 1, Name = "A" };
    var b = new MyClass { Id = 2, Name = "B" };

    collection.Add(a);
    collection.Add(b);

    // Other instance, that can come from external code
    var otherA = new MyClass { Id = 1, Name = "A" };
    var otherB = new MyClass { Id = 2, Name = "B" };

    collection.Remove(otherA);

    Console.WriteLine(collection.Count); // should output 1

    Console.WriteLine(collection.Contains(otherB)); // should be true

    collection.Add(otherB); // 

    Console.WriteLine(collection.Count); // should still output 1
}

In fact, only the ID should be considered when searching within the collection. This code is part of a project that rely on serialized DTO, and thus, cannot rely on instances.

How should I implement MyClassCollection?

I think about two possibilities, but neither looks like elegant for me :

  1. inherits from List<MyClass> and add new methods. This will leave the .Add and .Remove as is and can cause troubles for consumer code
  2. Create a class that implements ICollection<MyClass>. Create an internal Dictionary<int, MyClass> to store the values, and use the dictionary builtin features to check keys before adding or removing. Implement all methods of the interface by wrapping the internal dictionary values

I'd appreciate your point of view regarding my case.

PS: I expect to have very few elements in the list (<100 items). Performance won't be an issue

Edit: I'm using .Net 3.5 SP1.

Edit2: according Jon advise, here is my implementation :

public class MyClassCollection : HashSet<MyClass>
{
    private class MyClassIDComparer : IEqualityComparer<MyClass>{

        public bool Equals(MyClass x, MyClass y)
        {
            if (x == null && y == null) return true;
            else if (x == null || y == null) return false;
            else return x.ID == y.ID;
        }

        public int GetHashCode(MyClass obj)
        {
            return obj.ID.GetHashCode();
        }
    }
    public MyClassCollection() : base(new MyClassIDComparer())
    {

    }
}

Upvotes: 3

Views: 146

Answers (1)

Jon
Jon

Reputation: 437366

HashSet<MyClass> is a good fit for your usage case because it models your intent and can be configured with a custom IEqualityComparer<MyClass> (thus it doesn't matter that MyClass does not implement IEquatable<MyClass>).

Unfortunately HashSet does not implement IList, so it cannot directly do index-based access -- but do you really need that? Perhaps this requirement can be relaxed by rethinking some part of your business logic?

Upvotes: 2

Related Questions