我有一个类的层次结构,我希望使用XmlSerializer
类及其相关属性进行序列化。有一个基本抽象类,然后是相当多的派生类(在我的代码中,我已经将派生类的数量减少到五个,但实际代码中还有更多)。这些类形成一个层次结构,并且通常包含对层次结构中类的实例的引用。
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;
}
最后,一个容器可以将它们全部保存并传送到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;
}
正如您所看到的,我的代码中有相当多的重复。在某些时候,可能会引入一个新的派生类,并且必须使用新的XmlElement
属性重新访问所有使用BaseType的地方。这既繁琐又容易出错。我想表达的事实是,如果元素名称为“t”,则BaseType
可以反序列化为TType,但如果元素名称为“p”,则可以反序列为PType,等等。
我知道XmlIncludeAttribute
但它引入了“金牌所有者”不满意的xsi:type
属性。有没有办法分解XML元素名称和CLR类型之间的映射知识?
解决方案可以做出的一个假设是,定义BaseType
的程序集知道完整的派生类集。这意味着我们不必考虑在混合中添加新类的外部程序集。
答案 0 :(得分:2)
在摆弄了一段时间之后,我想出了一个解决方案并在这里张贴以帮助处于同样情况的其他人。
首先,找到类型层次结构中的所有类型。然后编写一个函数,构建一个包含所有类型的XmlElements
实例:
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)),
}
};
}
现在找到所有具有您希望序列化的上述类型之一的字段的类型。你可以通过反映程序集中的所有类来实现这一点,但在我的情况下,我碰巧知道只有少数类具有这个要求:
Type [] typesToOverride = new Type[]
{
typeof(PType),
typeof(SType),
typeof(AType),
typeof(UType),
typeof(ItemCollection),
};
我们现在继续创建一个XmlAttributeOverrides
实例,为相应类型的每个字段指定hierarchyTypeElements
覆盖:
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;
}
(是的,我在滥用Aggregate
; LinQ是如此整洁的金锤子)
最后使用XmlAttributeOverrides
实例创建序列化程序:
var attrOverrides = GetAttributeOverrides(TypesToDecorate);
serializer = new XmlSerializer(typeof(ItemCollection), attrOverrides);
您可能希望将该序列化程序缓存在静态变量中,以避免组件泄漏。
此代码可以通用化以装饰属性和字段。这留给读者练习。