I have two interfaces; ISet and ISet<T>
Both implement Add, Remove and Clear but with different argument types; object and T.
Now I have two options. Either let ISet<T> inherit from ISet, or let them be isolated from each other.
One great benefit of having ISet<T> inherit from ISet is of course that I always can use a ISet<T> where-ever I need a ISet. But doing so I also have to add the "new" modifier to the members with a T-parameter. I don't like the "new"-modifier very much, but maybee this is the better option in this case?
It feels odd having a ISet<T> that isn't also a ISet. That's the logical assumption. I can't really say excatly why I don't like the "new"-modifier. It's like with the "goto"-keyword. I just try not to use it.
What do you think I should go with here? Inherit or not?
In .NET we have ICollection<T> and ICollection which doesn't inherit. Neither do IList<T> and IList. But IEnumerable<T> inherit from IEnumerable.
I know about the ISet-interface that allready exist (This was just an example)
I've had this problem in other situations as well. I usually resolve it by having ISet<T> have its versions of the methods, and ISet have its own excepting object. Just as you have mentioned. In addition to this I also create an abstract class called BaseSet<T> that implements both ISet<T> and ISet like so
public abstract class BaseSet<T> : ISet<T>, ISet {
public abstract void Add<T>(T item);
void ISet.Add(object item) {
this.Add((T)item);
}
}
Then one only has to inherit from BaseSet<T> and implement the ISet<T> interface for the most common scenario, yet they can still fully implement ISet<T> and ISet if the BaseSet<T> convenience class does not fit what they need. It also removes the need for usage of the new keyword.
The only thing that inheriting from the non-generic form would really buy you is the ability to perform certain operations on an ISet<T> without having to know at compile time what the T was. One couldn't add items to such a set in type-safe manner, and reading might be somewhat inefficient (since value types would have to be boxed), but methods or properties like Count could be perfectly useful.
My suggestion would be that you define multiple interfaces something like this (note that I'm adding some members that you don't have, since your collection is at moment write-only and thus not very useful)
interface ICountable { int Count {get;} }
interface IClearable { int Count {get;} }
interface IAppendable<in T> { void Add(T item); }
interface ICountableEnumerable<out T> : IConvertableToEnumerable, ICountable
{IEnumerable<T> CopyAsEnumerable();}
interface IFetchable<out T> { T FetchAndRemove(ref bool wasNonEmpty); }
interface ISet<T> ICountable, IClearable, IAppendable<T>, IFetchable<T>,
IConvertableToEnumerable<T>;
Such interface segregation makes it possible to use covariance and contravariance to the extent that it makes sense.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With