Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with covariance when returning collection in c#?

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:

  1. I could synchronize the two lists but that seems to be extra work to do
  2. Return IEnumerable instead of IList and add a method for create/delete collections
  3. Create a custom collection that takes the TConcrete and TInterface

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.

like image 754
Khronos Avatar asked Sep 11 '11 16:09

Khronos


1 Answers

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 Items 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.

like image 94
svick Avatar answered Nov 15 '22 13:11

svick