问题
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