2009-09-10 4 views
5

J'essaie de mettre en œuvre un identity map en utilisant des génériques. J'ai une classe abstraite, Entity, et une contrainte de dérivation sur ma carte pour Entity. Puisque ma carte doit être capable d'instancier des entités, ma carte a aussi une contrainte de constructeur.Carte d'identité générique en C#. Je ne veux pas de constructeur public

Cependant, pour que la carte soit utile, les sous-classes Entity ne devraient pas pouvoir être instanciées à partir du code client, ce qui signifie que je voudrais un constructeur interne et pas de constructeur public. Ceci est en conflit avec la contrainte du constructeur, cependant.

Y a-t-il quelque chose qui me manque? Y at-il un moyen de refactoriser cela pour obtenir le résultat souhaité?

Le code suivant compile en l'état mais, idéalement, les constructeurs de sous-classes de l'entité seraient interne:

public abstract class Entity 
{ 
    public int Id { get; protected internal set; } 
} 

public sealed class Widget : Entity 
{ 
    // Client code should not be allowed to instantiate entities. 
    // But the constraints on EntityMap require that entities have 
    // a public constructor. 
    public Widget() { } 
} 

public sealed class Gadget : Entity 
{ 
    public Gadget() { } 
} 

// The new() constraint is required so that Get() can instantiate Ts. 
public class EntityMap<T> where T : Entity, new() 
{ 
    private Dictionary<int, T> _entities = new Dictionary<int, T>(); 
    private object _getLock = new object(); 

    public T Get(int id) 
    { 
     lock (_getLock) 
     { 
      if (!_entities.ContainsKey(id)) 
       _entities.Add(id, new T() { Id = id }); 
     } 

     return _entities[id]; 
    } 

    // Client code should not be allowed to instantiate maps. 
    internal EntityMap() { } 
} 

// Ideally, the client would only be able to obtain Entity 
// references through EntityMaps, which are only accessible 
// through the ApplicationMap. 
public static class ApplicationMap 
{ 
    public static EntityMap<Widget> Widgets = new EntityMap<Widget>(); 
    public static EntityMap<Gadget> Gadgets = new EntityMap<Gadget>(); 
} 

Répondre

9

Au lieu d'exiger une contrainte constructeur, passer un Func<T> au constructeur de la carte. De cette façon, le constructeur peut être interne, mais la carte peut toujours appeler efficacement:

public class EntityMap<T> where T : Entity 
{ 
    private readonly Dictionary<int, T> _entities = new Dictionary<int, T>(); 
    private readonly object _getLock = new object(); 
    private readonly Func<T> _entityGenerator; 

    public T Get(int id) 
    { 
     lock (_getLock) 
     { 
      T ret; 
      if (!_entities.TryGetValue(id, ret)) 
      { 
       ret = entityGenerator(); 
       newEntity[id] = ret; 
       ret.Id = id; 
      } 

      return ret; 
     } 
    } 

    internal EntityMap(Func<T> entityGenerator) 
    { 
     _entityGenerator = entityGenerator; 
    } 
} 

Puis l'initialiser avec:

EntityMap<Widget> widgetMap = new EntityMap(() => new Widget()); 

Vous pourriez potentiellement faire un Func<int, T> au lieu et rendre le délégué responsable de la créer une entité avec le bon ID. De cette façon, vous pouvez rendre votre ID correctement en lecture seule, en le prenant comme paramètre pour le constructeur Entity.

(. Je me suis permis de rendre votre méthode Get plus efficace, btw)

+0

Quel est l'avantage d'utiliser à la place TryGetValue de ContainsKey? Est-ce un problème de vitesse? – Lobstrosity

2

Merci à Jon, voici le code de travail:

public abstract class Entity 
{ 
    private readonly int _id; 

    public int Id 
    { 
     get { return _id; } 
    } 

    internal Entity(int id) 
    { 
     _id = id; 
    } 
} 

public sealed class Widget : Entity 
{ 
    internal Widget(int id) : base(id) { } 
} 

public sealed class Gadget : Entity 
{ 
    internal Gadget(int id) : base(id) { } 
} 

public class EntityMap<T> where T : Entity 
{ 
    private readonly Dictionary<int, T> _entities = new Dictionary<int, T>(); 
    private readonly object _getLock = new object(); 
    private readonly Func<int, T> _entityGenerator; 

    public T Get(int id) 
    { 
     lock (_getLock) 
     { 
      T entity; 

      if (!_entities.TryGetValue(id, out entity)) 
       _entities[id] = entity = _entityGenerator(id); 

      return entity; 
     } 
    } 

    internal EntityMap(Func<int, T> entityGenerator) 
    { 
     _entityGenerator = entityGenerator; 
    } 
} 

public static class ApplicationMap 
{ 
    public static readonly EntityMap<Widget> Widgets = new EntityMap<Widget>(id => new Widget(id)); 
    public static readonly EntityMap<Gadget> Gadgets = new EntityMap<Gadget>(id => new Gadget(id)); 
} 
+1

Vous feriez mieux de poster ceci comme une modification à votre message original au lieu d'une réponse alternative. – jpierson

Questions connexes