Chris Laplante
Chris Laplante

Reputation: 29668

How can I solve this C# polymorphism problem?

I have a base class, SpecialClass. Lots of other classes inherit from it, like WriteSpecialClass and ReadSpecialClass. Instances of these classes are serialized and deserialized, after being casted to/from the base class. Therefore, I have lots of repetitious code like this (for serializing):

SpecialClass sc = null;

if (type.DisplayName == "Read") {
    sc = new ReadSpecialClass();
} else if (type.DisplayName == "Write") {
    sc = new WriteSpecialClass();
} else
    throw new NotSupportedException("Type not supported");

String xml = SerializeToXML(sc);

.. and deserializing:

SpecialClass sc = null;

sc = (SpecialClass)DeserializeFromXML(xml);

switch (someVariableThatDicatesTypeOfSpecialClass) {
    case "Read":
        sc = (ReadSpecialClass)sc;
        break;
    case "Write":
        sc = (WriteSpecialClass)sc;
        break;
}

One more important piece of info: SpecialClass has a bunch of abstract methods defined which each class that inherits from it needs to implement. All the classes which inherit from it conform to the methods, but return different things, so each class is not the same.

I can post my serialization or deserialization methods if needed.

I would like to try and simplify this so that I don't have to specify each SpecialClass-derived class (like ReadSpecialClass). Is there any way to do this? The only way I can think of is duck-typing. Thanks for your help,

Upvotes: 0

Views: 611

Answers (5)

Tim Jarvis
Tim Jarvis

Reputation: 18825

Rule 1./ Your derived classes should be responsible for serializing and deserializing themselves, as they should be the only ones that have intimate knowlege of the additional data that is stored in the XML Doc.

So from a ploymorphic point of view (note your code above is not polymorphic) you would do something like

public class SpecialClass
{
  public virtual XElement SerializeToXML()
  {
    // Base impl
  }
}

public class YourDerivedClasses
{
  public override XElement SerializeToXML()
  {
    //derived implementation 
  }
}

Pretty straight forward. But the problem you have is not one of serialization or polymorphic behaviour, it's one of instantiation of the correct type from the XML.

One way to solve that problem is to have some key in your XML doc that specifies the type that saved it, and register a factory responsible for construction of the derived type indexed by that key (attributes are good for that kind of registration), once the derived type is constructed use it to deserialize the xml.

Note, your factory could also be a static method on the Base class.

Something like, (untested of course)....

public class SpecialClass
{
  ***snip
  public static IEnumerable<SpecialClass> GetAllClasses(XElement xml)
  {
    IDictionary keyedtypes = GetKeyedTypesDictUsingReflection() // the registered factories
    foreach(var x in xml.Elements("YourClassesNode"))
    {
      string key = //get key from xml
      yield return keyedTypes[key].DeserializeFromXML(x);
    }
  }
}

Upvotes: 0

Shuo
Shuo

Reputation: 4847

If you're not concerned about performance, you can use reflection to initialize the types whose name contains that type name.

SerializeToXML() takes the base class SpecialClass as a parameter, so it shouldn't distinguish the difference between the derived classes ReadSpecialClass and WriteSpecialClass. Why don't you have SerializeToXML() as an instance method of the base class?

Upvotes: 0

Tokk
Tokk

Reputation: 4502

if (sc is WriteSpecialClass)
{
    sc = (WriteSpecialClass) sc;
}
 else if (sc is ReadSpecialClass)
{
    sc = (ReadSpecialClass) sc;
}
else
{
    throw new NotSupportetException("Type not Supportet");
}

Upvotes: 0

Rob
Rob

Reputation: 4239

Have you considered a serialize() method in SpecialClass? That way if there are some special considerations you can override the base.serialize method and take care of any unique needs. In this way it already knows what it is and the conditional isn't necessary.

Another thing to consider is maybe a helper class with a facade pattern. Then you can have a a method called "public SpecialClass DeserializeSpecialClass()". Then instead of casting the type in the deserializer you could cast it at the target. If you find you are doing too much casting then maybe consider adding abstract methods to the base class that will be realized in the derived class.

Hope this helps.

Upvotes: 3

cdhowie
cdhowie

Reputation: 169498

For serializing, you can use some sort of lookup:

public class SpecialClass
{
    private static Dictionary<string, Func<SpecialClass>> factories =
        new Dictionary<string, Func<SpecialClass>>();

    static SpecialClass()
    {
        factories["Read"] = () => new ReadSpecialClass();
        factories["Write"] = () => new WriteSpecialClass();
    }

    public static SpecialClass CreateByName(string name)
    {
        Func<SpecialClass> factory;

        if (!factories.TryGetValue(name))
            throw new ArgumentException("name", "\"" name +
                "\" is not a recognized subclass of SpecialClass.");

        return factory();
    }
}

For deserialization, these two lines:

sc = (ReadSpecialClass)sc;
sc = (WriteSpecialClass)sc;

perform no actual conversion. The only thing they will do is throw an exception if the object referenced by sc is not of the appropriate type. What you are doing here is roughly the same thing as:

object a = "foo";
a = (string)a;

Sure, the cast will succeed. But it does not in any way modify the object pointed to by a. All it really does is verify that this object is already a string.

Upvotes: 2

Related Questions