UPDATE 2: Well, you say in a comment that you have not defined a non-generic CacheCollection
type; but then you go on to say that you have a Dictionary<Type, CacheCollection>
. These statements cannot both be true, so I am guessing that by CacheCollection
you mean CacheCollection<EntityBase>
.
Now here's the problem: a X<Derived>
cannot be cast to a X<Base>
if the X<T>
type is not covariant. That is, in your case, just because T
derives from EntityBase
does not mean that CacheCollection<T>
derives from CacheCollection<EntityBase>
.
For a concrete illustration of why this is, consider the List<T>
type. Say you have a List<string>
and a List<object>
. string
derives from object
, but it does not follow that List<string>
derives from List<object>
; if it did, then you could have code like this:
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));
Fortunately, the way around this (in my view) is pretty straightforward. Basically, go with my original suggestion by defining a non-generic base class from which CacheCollection<T>
will derive. Better yet, go with a simple non-generic interface.
interface ICacheCollection
{
EntityBase Item(int id);
}
(Take a look at my updated code below to see how you can implement this interface in your generic type).
Then for your dictionary, instead of a Dictionary<Type, CacheCollection<EntityBase>>
, define it as a Dictionary<Type, ICacheCollection>
and the rest of your code should come together.
UPDATE: It seems that you were withholding from us! So you have a non-generic CacheCollection
base class from which CacheCollection<T>
derives, am I right?
If my understanding of your latest comment to this answer is correct, here's my advice to you. Write a class to provide indirect access to this Dictionary<Type, CacheCollection>
of yours. This way you can have many CacheCollection<T>
instances without sacrificing type safety.
Something like this (note: code modified based on new update above):
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);
}
}
This would allow you to write code like the following:
var cache = new GeneralCache();
RedEntity red = cache.GetOrAddItem<RedEntity>(1, id => new RedEntity(id));
BlueEntity blue = cache.GetOrAddItem<BlueEntity>(2, id => new BlueEntity(id));
Well, if T
derives from EntityBase
but does not have a parameterless constructor, your best bet is going to be to specify a factory method that will generate a T
for the appropriate parameters in your CacheCollection<T>
constructor.
Like this (note: code modified based on new update above):
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);
}
}
I would also recommend, if your items are going to have unique IDs, to use a Dictionary<int, CacheItem<T>>
as your backing store instead of a List<CacheItem<T>>
as it will make your item lookup O(1) instead of O(N).
(I would also recommend implementing this class using a private member to hold the collection itself rather than inheriting from the collection directly, as using inheritance exposes functionality you probably want hidden such as Add
, Insert
, etc.)