7

I have an object (form) which contains a collection (.Fields) which I want to contain instances of a generic class (FormField).

The FormField, simply, is defined as such:

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;
    }
}

This allows me to have FormField, FormField etc. and that part works great. What I want is a collection of "Formfields" regardless of the type, but I am forced into defining a type (it seems) such as:

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; }
}

What, I think, I want is an interface that allows me to abstract the type information and thus type the collection as instances of (for exxample) IFormField not FormField<>

But I can't see how to define this without strongly typing the collection in the interface...

Any help (including any alternative solutions!) would be greatly appreciated!

Thanks, Ben

Ben
  • 73
  • 1
  • 4

3 Answers3

13

Here's some code to complete Jon's answer:

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; }
    }
}

And in your form:

ICollection<IFormField> Fields { get; set; }
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
6

Create a non-generic interface or base class, which probably includes everything FormField does except the type-specific bits. Then you can have an ICollection<IFormField>. Obviously you won't be able to use this in a strongly-typed way, in terms of the type of field being used - but you can use all the non-type-specific bits of it (e.g. the name and the form).

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    thanks Jon, but if I understand you correctly then the interface would effectively have everything but the value property, which is a key element of the FormField..? Am I trying to use generics in the wrong way perhaps? – Ben Sep 23 '10 at 09:52
  • 1
    @Ben: Consider how you would try to *use* this collection. It could have a mixture of different form field types (int, string etc). How would you try to use the values from those form fields within `Form`? You won't know them at compile-time, or you'll be explicitly casting to the right type. – Jon Skeet Sep 23 '10 at 09:59
  • @Ben, you can declare a Value property in the IFormField interface, but you will have to declare it as Object. In the FormField class, declare Value as T, and implement IFormField.Value explicitly. – Thomas Levesque Sep 23 '10 at 09:59
  • ahh, of course. I'm looking at this from the wrong angle. Cheers Jon, much appreciated – Ben Sep 23 '10 at 10:01
  • @Thomas Levesque - Cheers Thomas, I did try with the collection defined as type Object and it was causing me some problems, but I think Jon has prompted me to re-evaluate what I'm trying to do, and how! Thanks for your help – Ben Sep 23 '10 at 10:03
1

Another option (an alternative to Jon's answer) is to apply the adapter pattern, which can be useful when:

  • you are unable to modify the type, and can thus not define a base-type for it.
  • or, there is a need to expose 'type-specific bits' (as Jon put it).

When you want to expose type-specific bits, you effectively have to create a non-generic wrapper. A short example:

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; }
    }
}

Implementing this non-generic behavior in a base-type would effectively break the Liskov substitution principle, which is why I prefer the wrapper approach as I also argue in my blog post.

Community
  • 1
  • 1
Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161