So this is the code that I have tried, but it adds the same object more than once:
namespace TestComparison
{
public interface IAddable
{
int RandomIntValue { get; set; } // often Times this value will repeat.
}
public class AdditionManager<T> where T : IAddable
{
private List<T> addables;
public AdditionManager()
{
addables = new List<T>();
}
public void Add(T _addable)
{
if (!addables.Contains(_addable))
{
addables.Add(_addable);
}
}
}
public class TestAddable : IAddable
{
public int RandomIntValue { get; set; }
public Data UniqueData = new Data() { UniqueId = 10023 }; // This is what really make each item unique
}
public class Data
{
public int UniqueId { get; set; }
}
}
I've heard about the IEqualityComparer and I have implemented it in non-generic classes, but I'm not quite sure how to implement it here.
You can use dependency injection to provide you with generic implementation. Doing so you'll need to provide the custom IEqualityComparer<T>
implementation that you want at the point of generic object's construction.
public class AdditionManager<T> where T : IAddable
{
private List<T> addables;
private IEqualityComparer<T> comparer;
public AdditionManager()
: this (EqualityComparer<T>.Default)
{ }
public AdditionManager(IEqualityComparer<T> _comparer)
{
addables = new List<T>();
comparer = _comparer;
}
public void Add(T _addable)
{
if (!addables.Contains(_addable, comparer))
{
addables.Add(_addable);
}
}
}
However, if you are looking for you list of addables
to be unique based on some constraint, I would not use the above implementation for performance reasons. As the List<T>.Contains
check will become slower as the list grows larger.
If the order of the list does not matter change your List<T>
to a HashSet<T>
. HashSet<T>.Contains
will be just as quick as a Dictionary<TKey, TValue>
lookup. But this call can be avoided altogether with HashSet<T>
as the Add
call will first check to see if the item is in the set before adding it, and return true
or false
to indicate it was added or not`
So if the order of addables
is of not concern, then I would use the following implementation.
public class AdditionManager<T> where T : IAddable
{
private HashSet<T> addables;
public AdditionManager()
: this(EqualityComparer<T>.Default)
{ }
public AdditionManager(IEqualityComparer<T> _comparer)
{
addables = new HashSet<T>(_comparer);
}
public void Add(T _addable)
{
// will not add the item to the HashSet if it is already present
addables.Add(_addable);
}
}
If you need to maintain the order of addables
then I suggest maintaining the list of objects in both a HashSet<T>
and List<T>
. This will provide you with the performance of the above implementation, but maintain the addition order on your items. In this implementation any of the operations you need to perform, do them against the List<T>
and only use the HashSet<T>
to make sure the item isn't already present when adding to List<T>
If you are going to have some type of Remove
operation, make sure to remove the item from both the HashSet<T>
and List<T>
public class AdditionManager<T> where T : IAddable
{
private HashSet<T> set;
private List<T> list;
public AdditionManager()
: this(EqualityComparer<T>.Default)
{ }
public AdditionManager(IEqualityComparer<T> _comparer)
{
set = new HashSet<T>(_comparer);
list = new List<T>();
}
public void Add(T _addable)
{
if (set.Add(_addable))
list.Add(_addable);
}
}
To create this object using TestAddable
you'll need an IEqualityComparer<TestAddable>
like the following. As others have suggested, the field(s) you are doing your comparison on should be made immutable, as a mutable key is going to cause bugs.
public class TestAddableComparer : IEqualityComparer<TestAddable>
{
public bool Equals(TestAddable x, TestAddable y)
{
return x.UniqueData.Equals(y.UniqueData);
}
public int GetHashCode(TestAddable obj)
{
// since you are only comparing use `UniqueData` use that here for the hash code
return obj.UniqueData.GetHashCode();
}
}
Then to create the manager object do:
var additionManager = new AdditionManager<TestAddable>(new TestAddableComparer());