How to serialize class hierarchies whose classes refer to each to other, but avoiding XmlInclude?

我的梦境 提交于 2019-12-23 21:18:46

问题


I have a hierarchy of classes that I want to serialize using the XmlSerializer class and its related attributes. There is a base abstract class and then quite a few derived classes (in my code below, I've reduced the number of derived classes to five, but there are many more in the actual code). The classes form a hierarchy and frequently contain references to instances of classes in the hierarchy.

public abstract class BaseType 
{
    // Only classes in my assembly can derive from this class
    internal BaseType() { }   
}

public sealed class TType : BaseType
{
    [XmlText]
    public string Name;
}

public sealed class PType : BaseType
{
    [XmlElement("t", typeof(TType)]
    [XmlElement("p", typeof(PType)]
    [XmlElement("a", typeof(AType)]
    [XmlElement("s", typeof(SType)]
    [XmlElement("u", typeof(UType)]
    public BaseType Child;
}

public sealed class SType : BaseType
{
    [XmlElement("t", typeof(TType)]
    [XmlElement("p", typeof(PType)]
    [XmlElement("s", typeof(SType)]
    [XmlElement("a", typeof(AType)]
    [XmlElement("u", typeof(UType)]
    public BaseType [] Items;
    public string [] ItemNames;
}

public sealed class AType : BaseType
{
    [XmlElement("t", typeof(TType)]
    [XmlElement("p", typeof(PType)]
    [XmlElement("s", typeof(SType)]
    [XmlElement("a", typeof(AType)]
    [XmlElement("u", typeof(UType)]
    public BaseType Item;
    public int Length;
}

public sealed class UType : BaseType
{
    [XmlElement("t", typeof(TType)]
    [XmlElement("p", typeof(PType)]
    [XmlElement("s", typeof(SType)]
    [XmlElement("a", typeof(AType)]
    [XmlElement("u", typeof(UType)]
    public BaseType [] Alts;
    public string [] AltNames;
}

Finally, a container to hold them all and feed to the XmlSerializer:

[XmlRoot("items")]
public class ItemCollection
{
    [XmlElement("t", typeof(TType)]
    [XmlElement("p", typeof(PType)]
    [XmlElement("s", typeof(SType)]
    [XmlElement("a", typeof(AType)]
    [XmlElement("u", typeof(UType)] 
    public BaseType [] Items;
}

As you can see, there is quite a bit of repetition in my code. At some point, a new derived class may be introduced, and all of the places where references to BaseType are used will have to be re-visited with a new XmlElement attribute. This is tedious as well as error-prone. I'd like to express the fact that a BaseType can be deserialized as a TType if the element name is "t", but as PType if the element name is "p", etc, exactly once.

I'm aware of XmlIncludeAttribute but it introduces xsi:type attributes which the "gold-owner" is not happy with. Is there any way to factor out the knowledge of the mapping between XML element names and CLR types?

One assumption a solution can make is that the full set of derived classes is known by the assembly that defines BaseType. That means we don't have to consider external assemblies adding new classes into the mix.


回答1:


After fiddling around with this for a while, I came up with a solution and am posting here to help others in the same situation.

First, locate all the types in the type hierarchy. Then write a function that builds an XmlElements instance containing all those types:

XmlAttributes CreateXmlAttributesForHierarchyTypes()
{
    return XmlAttributes
    {
        XmlElements = 
        {
            new XmlElementAttribute("t", typeof (TType)),
            new XmlElementAttribute("p", typeof (PType)),
            new XmlElementAttribute("s", typeof (SType)),
            new XmlElementAttribute("a", typeof (AType)),
            new XmlElementAttribute("u", typeof (UType)),
        }
    };
}

Now locate all types that have fields of one of the types above that you wish to serialize. You could do this by reflecting all of the classes in the assembly, but in my case I happen to know that there are only a few classes that have this requirement:

Type [] typesToOverride = new Type[] 
{
    typeof(PType),
    typeof(SType),
    typeof(AType),
    typeof(UType),
    typeof(ItemCollection),
};

We now proceed by creating an instance of XmlAttributeOverrides that specifies the hierarchyTypeElements overrides for each field of the appropriate types:

public static XmlAttributeOverrides GetAttributeOverrides(IEnumerable<Type> typesToOverride)
{
    var overrides = typesToOverride
        .SelectMany(x => x.GetFields())  // Get a flat list of fields from all the types
        .Where(f => f.FieldType == typeof (BaseType))  // Must have the right type
        .Select(f => new
        {
            Field = f,
            Attributes = GetXmlAttributes(f)
        })
        .Where(f => f.Attributes != null)
        .Aggregate(
            new XmlAttributeOverrides(),
            (ov, field) =>
            { 
                ov.Add(field.Field.DeclaringType, field.Field.Name, field.Attributes); 
                return ov;
            });
    return overrides;
}

(Yeah, I'm abusing Aggregate; LinQ is such a neat golden hammer )

Finally use the XmlAttributeOverrides instance to create your serializer:

var attrOverrides = GetAttributeOverrides(TypesToDecorate);
serializer = new XmlSerializer(typeof(ItemCollection), attrOverrides);

You may want to cache that serializer in a static variable to avoid leaking assemblies.

This code can be generalized to also decorate the properties as well as the fields. This is left as an exercise to the reader.



来源:https://stackoverflow.com/questions/15455786/how-to-serialize-class-hierarchies-whose-classes-refer-to-each-to-other-but-avo

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!