views:

213

answers:

5

I am sure there is a clever way to handle this, but I just can't get it right in my head. Here is the scenario:

I have a base class with about 30 properties. Say that I have a property on this class called Type. For a subset of the instances of this class, I want to add a few more properties based on the Type property. In this case when I create the objects I can just create a subclass of the base that contains these extra properties. My problem is that a user can change the type based on a combo box, and I need to be able to either cast it back to the base class or change it to a totally different subclass with a different subset of additional properties. Hopefully this comes across clearly. Any recommendations on how to handle this?

Note: this is a brownfield application, so while the idea of a property bag is appealing, it is also not possible without major pain.

+2  A: 

Just have a list of properties ...

List<Properties> MyProps;

And add to it appropriately.

Noon Silk
+1  A: 

You might be able to do that if you start with a very modified factory pattern.

I'd create a base class, and whenever the Type property changes as a result of user input, the factory-ish class (I'll call him Bob) is responsible for returning a class of the specified type. You'd call to Bob, passing in the type and the current object, and Bob would return the specific subtype.

For example:

public class MyBase
{
    public string Type { get; set; }
    public string CommonProperty { get; set; }
}

public class ExtendedClass : MyBase
{
    public string ExtraProperty { get; set; }
}

public static class MyNotFactory
{
    public static MyBase Create(MyBase baseObject)
    {
        switch (baseObject.Type)
        {
            case "Extended":
                return baseObject as ExtendedClass;
                break;
            default:
                return baseObject;
                break;
        }
    }
}

(Note: I haven't checked the code)

Edit: You know... it's not really a factory pattern at all now I put it in code... I've renamed stuff.

Damovisa
In your example, Create isn't doing anything except returning its input in either switch case.
Josh Einstein
In the first case statement, it should return an object of type ExtendedClass. In the second, it should return an object of type MyBase.
Damovisa
But you're right - you'd have to examine the actual return type to work that out.
Damovisa
+1  A: 

I love design patterns as much as the next guy but I think what you're looking at here is just plain old .NET inheritence and a little bit of reflection.

I'd create the inheritence hierarchy as you see fit, using normal inheritence then look into Activator.CreateInstance (or a static factory method on the base class) to create instances of the objects.

Josh Einstein
I do actually have a factory to create the objects if they already exist, I guess where I got lost was once the object is already created and they change type. In this case do you pass in your current object and clone the base class props?
Codezy
You could, or you could back the object's properties with a property bag type of object. One design pattern you may want to look at for this would be memento which is just a serialization approach. If all your properties are in a Dictionary(Of String, String) or a more specialized property bag class you can easily pass state from one instance to the other.
Josh Einstein
Actually that should say Dictionary(Of String, Object)
Josh Einstein
A: 

In .Net (and other strongly typed, static languages) once an instance of a class in instantiated you cannot change that instance to be a different type.

The only way to 'change types' is to assign a different instance of a compatible type to the variable (if the variable will accept types implementing Foo and types Foo1 and Foo2 both implement/extend Foo then an instance of either can be assigned to the variable

If you wish to maintain some state in this transition you can either define a common way for one instance of an object to copy all relevant values from another compatible instance (perhaps through reflection if the fields/properties are of the same type and name) or make it easy to share the sub sections via delegation, this is however a dangerous practice id the subsection types are not immutable unless your design is structured to never care.

Your question sounds suspiciously like you would like to have the following:

class FooWithAdOns
{
    int Blah { get; set;}
    int Wibble { get; set }
    Type AddOn { get; set; }
    /* if AddOn type is AddOnX */
    int X { get; set; }
    /* if AddOn type is AddOnY */
    int Y { get; set; }
}

class AddOnX
{
    int X { get; set;}
}


class AddOnY
{
    int Y { get; set;}
}

Were the existence of X/Y on FooWithAddOn was dynamic.

There are two ways to achieve this, one possible now, but limited to UI interactions based on reflection using things like ITypedList. The other is much more powerful but is based on the c# 4.0 feature of dynamic dispatch, where the names of methods/properties are worked out at runtime.

If you are just interaction with the UI via something like a PropertyGrid then the first approach will work so long as, every time you 'list' of properties changes you rebuild the property grid, the latter will work in code perfectly but, for UI code, you will need to write a wrapper capable of doing much of what the ITypedList does to expose the current 'fake properties' to the reflective UI.

ShuggyCoUk
A: 

Thank you all for your answers. In the end I found this post: "Using reflection to copy base class properties"

I adapted that post a bit and ended up with this as my working solution. (in my example I made a class called Base and had a couple classes deriving from it):

  Private Shared Sub CopyBaseProperties(ByVal pSource As Base, ByVal pDestination As Base)

      Dim fields() As FieldInfo = GetType(Base).GetFields(BindingFlags.NonPublic Or BindingFlags.Public Or BindingFlags.Instance)
      For Each tmpField As FieldInfo In fields
         tmpField.SetValue(pDestination, tmpField.GetValue(pSource))
      Next

   End Sub
Codezy