2013-03-16 20 views
2

我有一個層次結構類,我想使用XmlSerializer類及其相關屬性進行序列化。有一個基本的抽象類,然後有很多派生類(在我的代碼中,我已經將派生類的數量減少到五個,但實際代碼中有更多)。這些類形成一個層次結構,並且經常包含對層次結構中類的實例的引用。如何序列化其類相互引用的類層次結構,但避免使用XmlInclude?

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

正如你可以看到,有一個在我的代碼相當多的重複。在某些情況下,可能會引入一個新的派生類,並且所有使用對BaseType的引用的位置都必須用新的XmlElement屬性重新訪問。這是乏味的,也是容易出錯的。我想表達一個事實,即如果元素名稱爲「t」,則可以將BaseType作爲TType反序列化,但如果元素名稱爲「p」等,則只能將其作爲PType反序列化一次。

我知道XmlIncludeAttribute,但它引入了「金主」不滿意的xsi:type屬性。有什麼辦法可以將XML元素名稱和CLR類型之間的映射知識分解出來嗎?

一個假設的溶液可以使是全套派生類的由限定BaseType組件是已知的。這意味着我們不必考慮外部程序集將新類添加到組合中。

回答

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

你可能想緩存是串行器在一個靜態變量,以避免泄漏組件。

此代碼可以推廣到也裝飾性能以及字段。這是作爲練習給讀者的。