1

短いです:

派生型をコレクションに追加すると成功するのに、派生型のジェネリックを追加しようとすると失敗するのはなぜですか?

「短い」コード:

//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>());
    }

長い取引: オンラインストアで作業しているため、カタログ管理に関連するすべてのリポジトリのコレクションをカタログリポジトリに保持したいと考えています。

アイデアは、1 つのリポジトリからカタログ全体を管理することです。

リポジトリ コレクションのタイプは Dictionary です)

BaseCatalogModel 派生型リポジトリをコレクションに追加できません。

上記に関する支援や、より良い実装のための提案をいただければ幸いです。

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
    }
}
4

1 に答える 1

1

したがって、これはジェネリックではかなり一般的な問題です。

2つのクラスclass Baseとを想像してくださいclass Aclass Aから派生しBaseます。

    public class Base { }

    public class A : Base { }

を考えてみましょうList<T>List<T>Base または A を保持するクラスを作成できます。

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

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

y のクラスは x のクラスの子孫でなければならないというのはよくある誤解ですが、x には like のメソッドがAdd(Base item)あり、y には like のメソッドがAdd(A item)あり、コンパイラが y でそれを保証することはできないため、これは当てはまりません。インターフェイスは x のインターフェイスと互換性があります。これは、 のインスタンスを のインスタンスList<A>として扱うList<Base>場合、Base のインスタンスまたは Base の別のサブクラスで Add が呼び出されるのを止めるものがないためです。

現在、互換性を保証できるインターフェースの一部があります。A は常に Base の代わりになるため、これらはクラス A のインスタンスを返す任意のパーツです。

インターフェイスがジェネリックのみを出力し、.net 4 を使用している場合、簡単な解決策があります。out ジェネリック修飾子:

    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 { }

この解決策は、2 つのケースでは機能しません。インターフェイスにアイテムを渡す必要があるとき、および.net 4を使用していないとき。

これらのケースの両方に対しても、さまざまなソリューションがあります。

1) 項目もインターフェイスに渡す必要があり、.net 4 を使用しています。型の安全性を維持する必要がある場合は、Base として渡します。ジェネリック メソッドで別の場所にラップします。

    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) .net 4 を使用していない場合は、他にもできることがあります。リポジトリにインターフェイスのベース バージョンを強制的に実装させます。

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);
    }
}

入力を強く型付けしたままにしたい場合にできることはまだありますが、今のところ私の答えは十分に長いと思います。

于 2012-07-28T02:27:00.227 に答える