Roi Shabtai
Roi Shabtai

Reputation: 3085

Repositories collection

Is short:

How come that adding a derived type to collection passes but when trying to add a generic of the derived type it fails?

The "short" code:

//a generic repository
        public class EfRepository<T> : IRepository<T> where T: BaseCatalogModel{...}


        public CatalogRepository(IRepository<Product> productRepository, IRepository<Category> categoryRepository)
    {
        //This passes
        Dictionary<int, BaseCatalogModel> dic1 = new Dictionary<int, BaseCatalogModel>();
        dic1.Add(1, new Product());
        dic1.Add(2, new Category());
        dic1.Add(3, new BaseCatalogModel());


        //This not. 
        //The error: cannot convert from 'YoYo.Core.Data.Repositories.EfRepository<YoYo.Commerce.Common.Domain.Catalog.Product>' 
        //to 'YoYo.Core.Data.Repositories.EfRepository<YoYo.Commerce.Common.Domain.Catalog.BaseCatalogModel>'
        Dictionary<int, EfRepository<BaseCatalogModel>> dic2 = new Dictionary<int, EfRepository<BaseCatalogModel>>();
        dic2.Add(1, new EfRepository<Product>());
        dic2.Add(2, new EfRepository<Category>());
    }

The long deal: Working on an on-line store, I would like to hold in a catalog repository a collection of all repositories relevant to managing the catalog.

The idea is to manage the entire catalog from one repository.

The repositories collection is of type Dictionary)

I fail to add any BaseCatalogModel derived type repository to the collection.

I will be happy to get any assistance on the above or suggestions for better implementations.

public class BaseCatalogModel
{
    public int Id { get; set; }
    ...
}
public class Category:BaseCatalogModel
{
    ...
}
public class Product : BaseCatalogModel
{
    ...
}


public class CatalogRepository : ICatalogRepository 
{
    private readonly Dictionary<Type, IRepository<BaseEntity>> _repositoriesCollection= new Dictionary<Type, IRepository<BaseEntity>>();
    public CatalogRepository(IRepository<Product> productRepository, IRepository<Category> categoryRepository)
    {
        _repositoriesCollection.Add(typeof(Category), categoryRepository); //==> this fails 
        _repositoriesCollection.Add(typeof(Product), productRepository);    //==> this fails
    }


    public T GetCatalogItem<T>(int id) where T : BaseCatalogModel
    {
        //returns a catalog item using type and id
    }

    public IEnumerable<T> GetCatalogItem<T>() where T : BaseCatalogModel
    {
        //returns the entire collection of catalog item
    }
}

Upvotes: 1

Views: 1899

Answers (1)

Neil
Neil

Reputation: 1633

So this is a fairly common problem with generics.

imagine 2 classes class Base and class A. class A descends from Base.

    public class Base { }

    public class A : Base { }

Now consider List<T>. You can make classes from List<T> to hold Base or A:

List<Base> x = new List<Base>();

and

List<A> y = new List<A>();

It is a common misconception that the class of y must be a descendant of the class of x, but this cannot be true because x has a methods like Add(Base item) and y has a methods like Add(A item) and it is not possible for the compiler to guarantee that on y the interface will be compatible with the interface of x. this is because if you treat an instance of List<A> as an instance of List<Base> there is nothing to stop Add being called with an instance of Base or another subclass of Base.

Now there are some parts of the interface that can be guarantee as compatible. They are any parts that return an instance of class A, since A can always take the place of Base.

If your interface only output the generic and you are using .net 4 there is an easy solution. The out generic modifier:

    public class Example
    {

        private readonly Dictionary<Type, IRepository<Base>> _repositoriesCollection =
            new Dictionary<Type, IRepository<Base>>();

        public void DoSomething()
        {
            _repositoriesCollection.Add(typeof(A), new Repository<A>());
        }
    }


    interface IRepository<out T> where T : Base
    {
        T MakeSomeItem(string info);
        //void AddSomeItem(string info, T itemToAdd); <- this will not 
                                                        // work because T
                                                        // is out - so can't 
                                                        // go in... 
        IEnumerable<T> MakeSomeListOfItems(); // This is OK because 
             // IEnumerable<T> is declared as IEnumerable<out T> in the fx

        //List<T> Something(); <- not ok because List<T> is not List<out T>
    }

    public class Repository<T> : IRepository<T> where T : Base
    {
        public T MakeSomeItem(string info)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<T> MakeSomeListOfItems()
        {
           throw new NotImplementedException();
        }
    }

    public class Base { }

    public class A : Base { }

This solution will not work for 2 cases; when you need to pass an item into you interface and when you are not using .net 4.

There are numerous different solutions for both of those case also.

1) I need to pass an item into the interface too and I am using .net 4 - just pass it as Base, if you need to maintain type safety wrap it with a generic method somewhere else.

    interface IRepository<out T> where T : Base
    {
        T MakeSomeItem(string info);
        void AddSomeItem(string info, Base itemToAdd);
    }

    public class Repository<T> : IRepository<T> where T : Base
    {
        public T MakeSomeItem(string info){ throw new NotImplementedException(); }

        public void AddSomeItem(string info, Base itemToAdd)
        {
            T castedItem = (T) itemToAdd; //fails here at 
                                          //run time if not 
                                          // correct type
            AddSomeItem(info, itemToAdd);
        }

        public void AddSomeItem(string info, T itemToAdd)
        {
            /// do it for real...
        }
    }

2) If you are not working with .net 4 then there are other things that you can do too, force the repository to implement the Base version of your interface:

interface IRepository<T> where T : Base
{
    T MakeSomeItem(string info);
    void AddSomeItem(string info, T itemToAdd)
}

public class Repository<T> : IRepository<Base>, IRepository<T> where T : Base
{
    public T MakeSomeItem(string info) { throw new NotImplementedException(); }

    public void AddSomeItem(string info, Base itemToAdd)
    {
        T castedItem = (T) itemToAdd; //fails here at 
                                      //run time if not 
                                      // correct type
        AddSomeItem(info, itemToAdd);
    }

    public void AddSomeItem(string info, T itemToAdd)
    {
        /// do it for real...
    }

    Base IRepository<Base>.MakeSomeItem(string info)
    {
        return MakeSomeItem(info);
    }
}

There is still yet more that you can do if you want to keep your input strongly typed - but I think my answer is long enough for now.

Upvotes: 1

Related Questions