2010-09-23 5 views
5

J'ai un objet (formulaire) qui contient une collection (.Fields) que je veux contenir des instances d'une classe générique (FormField).Collection de types génériques

Le FormField, tout simplement, est défini comme tel:

public class FormField<T> 
{ 
    private Form Form; 
    public T Value { get; set; } 
    public string Name { get; set; } 

    public void Process() 
    { 
     // do something 
    } 

    public FormField(Form form, string name, T value) 
    { 
     this.Name = name; 
     this.Value = value; 
     this.Form = form; 
    } 
} 

Cela me permet d'avoir FormField, FormField etc., et cette partie fonctionne très bien. Ce que je veux est une collection de « FormFields » quel que soit le type, mais je suis forcé dans la définition d'un type (il semble) tels que:

public class Form 
{ 

    string Code { get; set; } 
    string Title { get; set; } 
    int Year { get; set; } 
    Guid ClientID { get; set; } 

    ICollection<FormField<int>> Fields { get; set; } 
} 

Qu'est-ce que je pense, je veux est une interface qui permet moi abstraire les informations de type et donc taper la collection comme des instances de (pour exxample) IFormField pas FormField <>

Mais je ne vois pas comment définir ce sans avoir à taper fortement la collection dans l'interface ...

Toute aide (y compris les solutions alternatives!) Serait grandement appréciée!

Merci, Ben

Répondre

4

Créer une interface non-générique ou classe de base, ce qui inclut probablement tout FormField fait sauf les bits de type spécifique. Ensuite, vous pouvez avoir un ICollection<IFormField>. Évidemment, vous ne serez pas en mesure de l'utiliser de manière fortement typée, en termes de type de champ utilisé - mais vous pouvez utiliser tous les bits qui ne sont pas spécifiques au type (par exemple le nom et le formulaire).

+1

merci Jon, mais si je vous comprends bien alors l'interface aurait effectivement tout sauf la propriété value, qui est un élément clé de la FormField ..? Est-ce que j'essaie d'utiliser des génériques dans le mauvais sens peut-être? – Ben

+1

@Ben: Considérez comment vous essaieriez * d'utiliser * cette collection. Il pourrait avoir un mélange de différents types de champs de formulaire (int, string, etc.). Comment essaieriez-vous d'utiliser les valeurs de ces champs de formulaire dans 'Form'? Vous ne les connaitrez pas au moment de la compilation, ou vous lancerez explicitement le bon type. –

+0

@Ben, vous pouvez déclarer une propriété Value dans l'interface IFormField, mais vous devrez le déclarer en tant qu'objet. Dans la classe FormField , déclarez Value comme T et implémentez IFormField.Value explicitement. –

9

est ici un code pour compléter la réponse de Jon:

public interface IFormField 
{ 
    string Name { get; set; } 
    object Value { get; set; } 
} 

public class FormField<T> : IFormField 
{ 
    private Form Form; 
    public T Value { get; set; } 
    public string Name { get; set; } 

    public void Process() 
    { 
     // do something 
    } 

    public FormField(Form form, string name, T value) 
    { 
     this.Name = name; 
     this.Value = value; 
     this.Form = form; 
    } 

    // Explicit implementation of IFormField.Value 
    object IFormField.Value 
    { 
     get { return this.Value; } 
     set { this.Value = (T)value; } 
    } 
} 

Et dans votre forme:

ICollection<IFormField> Fields { get; set; } 
0

Une autre option (une alternative à Jon's answer) est d'appliquer la adapter pattern, qui peut être utile lorsque:

  • vous ne pouvez pas modifier ify le type, et ne peut donc pas définir un type de base pour cela. Ou
  • ou, il est nécessaire d'exposer des 'bits spécifiques au type' (comme Jon l'a dit).

Lorsque vous souhaitez exposer des bits spécifiques au type, vous avez effectivement have to create a non-generic wrapper. Un court exemple:

class NonGenericWrapper<T> : IAdaptor 
{ 
    private readonly Adaptee<T> _adaptee; 

    public NonGenericWrapper(Adaptee<T> adaptee) 
    { 
     _adaptee = adaptee; 
    } 

    public object Value 
    { 
     get { return _adaptee.Value; } 
     set { _adaptee.Value = (T) value; } 
    } 
} 

La mise en œuvre de ce comportement non-générique dans un type de base aurait pour effet de briser le Liskov substitution principle, ce qui est la raison pour laquelle je préfère l'approche d'emballage comme I also argue in my blog post.

Questions connexes