2

我有一个类层次结构,我想使用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 引用的地方。这既乏味又容易出错。我想表达一个事实,BaseType如果元素名称为“t”,则 a 可以反序列化为 TType,但如果元素名称为“p”等,则为 PType,恰好一次。

我知道,XmlIncludeAttribute但它引入xsi:type了“金主”不满意的属性。有什么方法可以分解出 XML 元素名称和 CLR 类型之间的映射知识?

解决方案可以做出的一个假设是,定义BaseType. 这意味着我们不必考虑将新类添加到组合中的外部程序集。

4

1 回答 1

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);

您可能希望将该序列化程序缓存在静态变量中以避免泄漏程序集。

这段代码可以概括为也装饰属性和字段。这留给读者作为练习。

于 2013-03-22T13:32:51.860 回答