Avoid repeating myself (DRY) with DataContractSerializer and inheritance

130 Views Asked by At

I am trying to write a file header which is a XML string representing some object hierarchy.

This hierarchy at certain points contains subtypes so, for example the class Plant contains a property of type Stem, but upon serialization, Root value can be an instance of any subclass of Stem, like LongStem, DryStem or RottenStem. Also, there is a property Collection<LeafBase> Leaves whose items can be any subtype of LeafBase, like GreenLeaf, SweetLeaf, etc.

If I only use the default code, DataContractSerializer will give a runtime error complaining about "unexpected type", since it expects an object of Type Stem and receives a type DryStem instead, for example.

So, some research quickly led me to the Known Types solution, which would demand me to include an array with every subclass of the target type (and God knows how many additional layers).

Well this sounds to me like an outrageous violation of DRY and SRP principles, because if every time I add a subclass, I have to go around my source code looking for lists of known types to update (this is the Shotgun Surgery anti-pattern, and is one of the worst characteristics of the previous version of this system).

I have seen a way to use reflection and get said list of Known Types, but it's sort of hackish I guess (not that I care), so my question is:

What would be a good way to handle DataContractSerialization for a class whose object tree contains a lot of inheritance, regarding DRY and the known-type-list issue?

Alternatively, if there is another type-safe way to serialize and deserialize an object to a XML string, that is immune to this problem, that could be an option.

1

There are 1 best solutions below

4
Yuval Itzchakov On

If I understand correctly what you mean, you can avoid the duplication of declaring your sub-types each time a new one is created by discovering all the types deriving your base class at runtime.

Something along the lines of:

[KnownType("GetKnownTypes")]
public abstract class Foo
{
    public static Type[] GetKnownTypes()
    {
        Type currentType = MethodBase.GetCurrentMethod().DeclaringType;
        return currentType.Assembly.GetTypes()
                                   .Where(t => t.IsSubclassOf(currentType))
                                   .ToArray();
    }
}

This would let you set all known types at run-time, without needing to duplicate your KnownTypes attribute on each derived type.

Edit:

Could you suggest a way to get types from every assembly in solution instead of just the one where base class is declared?

Assuming you have a single AppDomain and the assemblies, you could search AppDomain.CurrentDomain.GetAssemblies(), which will iterate all types in all loaded assemblies in the current AppDomain:

public static Type[] GetKnownTypes()
{
    Type currentType = MethodBase.GetCurrentMethod().DeclaringType;
    return AppDomain.CurrentDomain.GetAssemblies()
                                  .SelectMany(x => x.DefinedTypes)
                                  .Where(x => x.IsSubclassOf(currentType))
                                  .ToArray();
}