ОБНОВЛЕНИЕ 2 : Вы говорите в комментарии, что не определили неуниверсальный тип CacheCollection
;но затем вы продолжаете говорить, что у вас есть Dictionary<Type, CacheCollection>
.Оба эти утверждения не могут быть правдой, поэтому я предполагаю, что под CacheCollection
вы подразумеваете CacheCollection<EntityBase>
.
. Теперь вот проблема: X<Derived>
не может быть приведен к X<Base>
, если X<T>
тип не является ковариантным .То есть, в вашем случае, то, что T
происходит от EntityBase
, не означает, что CacheCollection<T>
происходит от CacheCollection<EntityBase>
.
Для конкретной иллюстрации, почему это так, рассмотрим List<T>
тип.Скажем, у вас есть List<string>
и List<object>
.string
происходит от object
, но из этого не следует, что List<string>
происходит от List<object>
;если бы это было так, то у вас мог бы быть такой код:
var strings = new List<string>();
// If this cast were possible...
var objects = (List<object>)strings;
// ...crap! then you could add a DateTime to a List<string>!
objects.Add(new DateTime(2010, 8, 23));
К счастью, способ обойти это (на мой взгляд) довольно прост.По сути, следуйте моему первоначальному предложению, определив неуниверсальный базовый класс, из которого CacheCollection<T>
будет получать .Более того, используйте простой неуниверсальный интерфейс.
interface ICacheCollection
{
EntityBase Item(int id);
}
(посмотрите на мой обновленный код ниже, чтобы узнать, как вы можете реализовать этот интерфейс в своем универсальном типе).
Тогда для вашего словаря вместо Dictionary<Type, CacheCollection<EntityBase>>
определите его как Dictionary<Type, ICacheCollection>
, а остальной код должен собраться вместе.
ОБНОВЛЕНИЕ : Похоже, что выот нас воздерживались!Итак, у вас есть неуниверсальный базовый класс CacheCollection
, из которого происходит CacheCollection<T>
, я прав?
Если мое понимание вашего последнего комментария к этому ответу верно, вот мой совет для вас.Напишите класс, чтобы обеспечить косвенный доступ к вашему Dictionary<Type, CacheCollection>
.Таким образом, вы можете иметь множество CacheCollection<T>
экземпляров без ущерба для безопасности типов.
Примерно так ( примечание: код, измененный на основе new , обновление выше ):
class GeneralCache
{
private Dictionary<Type, ICacheCollection> _collections;
public GeneralCache()
{
_collections = new Dictionary<Type, ICacheCollection>();
}
public T GetOrAddItem<T>(int id, Func<int, T> factory) where T : EntityBase
{
Type t = typeof(T);
ICacheCollection collection;
if (!_collections.TryGetValue(t, out collection))
{
collection = _collections[t] = new CacheCollection<T>(factory);
}
CacheCollection<T> stronglyTyped = (CacheCollection<T>)collection;
return stronglyTyped.Item(id);
}
}
Это позволит вам написать код, подобный следующему:
var cache = new GeneralCache();
RedEntity red = cache.GetOrAddItem<RedEntity>(1, id => new RedEntity(id));
BlueEntity blue = cache.GetOrAddItem<BlueEntity>(2, id => new BlueEntity(id));
Хорошо, если T
происходит от EntityBase
, но не имеет конструктора без параметров, вашЛучше всего будет указать фабричный метод, который сгенерирует T
для соответствующих параметров в конструкторе CacheCollection<T>
.
Примерно так ( примечание: код, измененный на основе новый обновление выше ):
public class CacheCollection<T> : List<CacheItem<T>>, ICacheCollection where T : EntityBase
{
private Func<int, T> _factory;
public CacheCollection(Func<int, T> factory)
{
_factory = factory;
}
// Here you can define the Item method to return a more specific type
// than is required by the ICacheCollection interface. This is accomplished
// by defining the interface explicitly below.
public T Item(int id)
{
// Note: use FirstOrDefault, as First will throw an exception
// if the item does not exist.
CacheItem<T> result = this.Where(t => t.Entity.Id == id)
.FirstOrDefault();
if (result == null) //item not yet in cache, load it!
{
T entity = _factory(id);
// Note: it looks like you forgot to instantiate your result variable
// in this case.
result = new CacheItem<T>(entity);
Add(result);
}
return result.Entity;
}
// Here you are explicitly implementing the ICacheCollection interface;
// this effectively hides the interface's signature for this method while
// exposing another signature with a more specific return type.
EntityBase ICacheCollection.Item(int id)
{
// This calls the public version of the method.
return Item(id);
}
}
Я также рекомендовал бы, если ваши товары будут иметь уникальные идентификаторы, использовать Dictionary<int, CacheItem<T>>
в качестве резервного хранилища вместо List<CacheItem<T>>
так как это сделает поиск элементов O (1) вместо O (N).
(я бы также рекомендовал реализовать этот класс с использованием закрытого члена для хранения самой коллекции, а не наследования от коллекции напрямую,как использование наследования выставляет Fнеобъективность, которую вы, вероятно, хотите скрыть, например Add
, Insert
и т. д.)