MISE À JOUR 2: Eh bien, vous dites dans un commentaire que vous n'avez pas défini un type non-générique CacheCollection
; mais alors vous continuez à dire que vous avez un Dictionary<Type, CacheCollection>
. Ces déclarations ne peuvent pas être vraies, donc je devine que par CacheCollection
vous voulez dire CacheCollection<EntityBase>
.
Maintenant, voici le problème: un X<Derived>
ne peut pas être jeté à un X<Base>
si le type X<T>
is not covariant. C'est, dans votre cas, juste parce que T
dérive de EntityBase
ne signifie pas que CacheCollection<T>
dérive de CacheCollection<EntityBase>
.
Pour une illustration concrète de la raison, reportez-vous au type List<T>
. Supposons que vous avez un List<string>
et un List<object>
. string
dérive de object
, mais il ne s'ensuit pas que List<string>
dérive de List<object>
; si elle l'a fait, alors vous pourriez avoir le code comme ceci:
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));
Heureusement, la façon de contourner cela (à mon avis) est assez simple. Fondamentalement, aller avec ma suggestion originale en définissant une classe de base non générique à partir de laquelle CacheCollection<T>
dérivera. Mieux encore, optez pour une interface non générique simple. (Consultez mon code mis à jour ci-dessous pour voir comment implémenter cette interface dans votre type générique).
Ensuite, pour votre dictionnaire, au lieu d'un Dictionary<Type, CacheCollection<EntityBase>>
, définissez-le comme un Dictionary<Type, ICacheCollection>
et le reste de votre code devrait se réunir.
MISE À JOUR: Il semble que vous interdise de nous! Vous avez donc une classe de base non générique CacheCollection
dont dérive CacheCollection<T>
, ai-je raison? Si ma compréhension de votre dernier commentaire à cette réponse est correcte, voici mon conseil pour vous. Ecrivez une classe pour fournir un accès indirect à cette Dictionary<Type, CacheCollection>
.De cette façon, vous pouvez avoir beaucoup de CacheCollection<T>
instances sans sacrifier la sécurité de type.
Quelque chose comme ça (Note: le code modifié en fonction de nouvelle mise à jour ci-dessus):
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);
}
}
Cela vous permet d'écrire du code comme le suivant:
var cache = new GeneralCache();
RedEntity red = cache.GetOrAddItem<RedEntity>(1, id => new RedEntity(id));
BlueEntity blue = cache.GetOrAddItem<BlueEntity>(2, id => new BlueEntity(id));
Eh bien, si T
dérive de EntityBase
mais n'a pas un paramètre sans tructor, votre meilleur pari va être de spécifier une méthode d'usine qui va générer un T
pour les paramètres appropriés dans votre constructeur CacheCollection<T>
.
Vous aimez cette (Note: le code modifié en fonction de nouvelle mise à jour ci-dessus):
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);
}
}
Je recommande également, si vos articles vont avoir un ID unique, d'utiliser un Dictionary<int, CacheItem<T>>
comme backing store au lieu d'un List<CacheItem<T>>
car cela fera que votre objet recherche O (1) au lieu de O (N).
(je recommande également la mise en œuvre de cette classe à l'aide d'un parlementaire pour tenir la collection elle-même plutôt que d'hériter de la collection directement, en utilisant l'héritage expose les fonctionnalités que vous voulez probablement cachés tels que Add
, Insert
, etc.)
Renvoyé pour les recommandations en bas. Je ne suis pas sûr si j'aime l'usine dans cette solution. –
Merci, Dan. Cela semble définitivement être sur la bonne voie. Cela m'a amené à une autre question, cependant. Que devrais/devrais-je mettre dans la fonction _factory? Y a-t-il un moyen de rendre ce générique aussi ou ai-je besoin de 100 méthodes différentes pour représenter les 100 classes différentes qui pourraient aller dans ma collection? –
@Jeroen: Je ne peux pas dire que c'est la plus jolie solution, mais c'est flexible. Supposons que l'OP ne veuille pas définir un constructeur sans paramètre pour son type, et/ou qu'il souhaite que la propriété 'Id' soit en lecture seule. Une autre approche consisterait à définir une méthode 'CreateEntity' abstraite protégée, mais cela forcerait l'OP à hériter de' CacheCollection' pour chaque type pour lequel il veut avoir une collection (pas très attrayant). Personnellement, je trouve que la contrainte 'new' est utile dans les cas où un constructeur sans paramètre existe, mais pas où il faudrait en ajouter un juste pour satisfaire la contrainte. –