I have a problem with returning collection and covariance and I was wondering if anyone has a better solution.
The scenario is this:
I have 2 version of implementation and I would like to keep the version implementation completely separate (even though they might have the same logic). In the implementation, I would like to return a list of items and therefore in the interface, I would return a list of interface of the item. However, in the actual implementation of the interface, I would like to return the concrete object of the item. In code, it looks something like this.
interface IItem
{
// some properties here
}
interface IResult
{
IList<IItem> Items { get; }
}
Then, there would be 2 namespaces which has concrete implementation of these interfaces. For example,
Namespace Version1
class Item : IItem
class Result : IResult
{
public List<Item> Items
{
get { // get the list from somewhere }
}
IList<IItem> IResult.Items
{
get
{
// due to covariance, i have to convert it
return this.Items.ToList<IItem>();
}
}
}
There will be another implementation of the same thing under namespace Version2.
To create these objects, there will be a factory that takes the version and create the appropriate concrete type as needed.
If the caller knows the exact version and does the following, the code works fine
Version1.Result result = new Version1.Result();
result.Items.Add(//something);
However, I would like the user to be able to do something like this.
IResult result = // create from factory
result.Items.Add(//something);
But, because it's been converted to another list, the add will not do anything because the item will not be added back to the original result object.
I can think of a few solutions such as:
I understand why this is happening (due to the type safe and all), but none of workaround I think can of seems very elegant. Does anybody have better solutions or suggestions?
Thanks in advance!
Update
After thinking about this more, I think I can do the following:
public interface ICustomCollection<TInterface> : ICollection<TInterface>
{
}
public class CustomCollection<TConcrete, TInterface> : ICustomCollection<TInterface> where TConcrete : class, TInterface
{
public void Add(TConcrete item)
{
// do add
}
void ICustomCollection<TInterface>.Add(TInterface item)
{
// validate that item is TConcrete and add to the collection.
// otherwise throw exception indicating that the add is not allowed due to incompatible type
}
// rest of the implementation
}
then I can have
interface IResult
{
ICustomCollection<IItem> Items { get; }
}
then for implementation, I will have
class Result : IResult
{
public CustomCollection<Item, IItem> Items { get; }
ICustomCollection<TItem> IResult.Items
{
get { return this.Items; }
}
}
that way, if the caller is accessing the Result class, it will go through the CustomCollection.Add(TConcrete item) which is already TConcrete. If the caller is accessing through IResult interface, it will go through customCollection.Add(TInterface item) and the validation will happen and make sure the type is actually TConcrete.
I will give it a try and see if this would work.
The problem you are facing is because want to expose a type that says (among other things) “you can add any IItem
to me”, but what it actually will do is “You can add only Item
s to me”. I think the best solution would be to actually expose IList<Item>
. The code that uses this will have to know about the concrete Item
anyway, if it should add them to the list.
But if you really want to do this, I think the cleanest solution would be 3., if I understand you correctly. It's going to be a wrapper around IList<TConcrete>
and it will implement IList<TInterface>
. If you try to put something into it that is not TConcrete
, it will throw an exception.
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