簡體   English   中英

XML 接口屬性序列化

[英]XML serialization of interface property

我想 XML 序列化一個 object ,它(除其他外)具有IModelObject類型的屬性(這是一個接口)。

public class Example
{
    public IModelObject Model { get; set; }
}

當我嘗試序列化此 class 的 object 時,收到以下錯誤:
“無法序列化 Example 類型的成員 Example.Model,因為它是一個接口。”

我知道問題是接口無法序列化。 但是,具體的Model object 類型直到運行時才知道。

用抽象或具體類型替換IModelObject接口並使用帶有 XMLInclude 的 inheritance 是可能的,但似乎是一個丑陋的解決方法。

有什么建議么?

這只是聲明性序列化的固有限制,其中類型信息未嵌入輸出中。

在嘗試將<Flibble Foo="10" />轉換回

public class Flibble { public object Foo { get; set; } }

序列化程序如何知道它是否應該是 int、string、double(或其他)...

要完成這項工作,您有多種選擇,但如果您真的不知道直到運行時,最簡單的方法可能是使用XmlAttributeOverrides

遺憾的是,這只適用於基類,而不適用於接口。 您可以做的最好的事情就是忽略不足以滿足您的需求的屬性。

如果你真的必須使用接口,你有三個真正的選擇:

隱藏它並在另一個屬性中處理它

丑陋,令人不快的樣板和大量重復,但該類的大多數消費者不必處理這個問題:

[XmlIgnore()]
public object Foo { get; set; }

[XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized 
{ 
  get { /* code here to convert any type in Foo to string */ } 
  set { /* code to parse out serialized value and make Foo an instance of the proper type*/ } 
}

這很可能成為維護的噩夢……

實現 IXmlSerializable

與第一個選項類似,您可以完全控制事物,但

  • 優點
    • 你沒有令人討厭的“假”屬性。
    • 您可以直接與 xml 結構進行交互,增加靈活性/版本控制
  • 缺點
    • 您可能最終不得不為類上的所有其他屬性重新實現輪子

重復工作的問題與第一個類似。

修改您的屬性以使用包裝類型

public sealed class XmlAnything<T> : IXmlSerializable
{
    public XmlAnything() {}
    public XmlAnything(T t) { this.Value = t;}
    public T Value {get; set;}

    public void WriteXml (XmlWriter writer)
    {
        if (Value == null)
        {
            writer.WriteAttributeString("type", "null");
            return;
        }
        Type type = this.Value.GetType();
        XmlSerializer serializer = new XmlSerializer(type);
        writer.WriteAttributeString("type", type.AssemblyQualifiedName);
        serializer.Serialize(writer, this.Value);   
    }

    public void ReadXml(XmlReader reader)
    {
        if(!reader.HasAttributes)
            throw new FormatException("expected a type attribute!");
        string type = reader.GetAttribute("type");
        reader.Read(); // consume the value
        if (type == "null")
            return;// leave T at default value
        XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
        this.Value = (T)serializer.Deserialize(reader);
        reader.ReadEndElement();
    }

    public XmlSchema GetSchema() { return(null); }
}

使用它會涉及到類似(在項目 P 中):

public namespace P
{
    public interface IFoo {}
    public class RealFoo : IFoo { public int X; }
    public class OtherFoo : IFoo { public double X; }

    public class Flibble
    {
        public XmlAnything<IFoo> Foo;
    }


    public static void Main(string[] args)
    {
        var x = new Flibble();
        x.Foo = new XmlAnything<IFoo>(new RealFoo());
        var s = new XmlSerializer(typeof(Flibble));
        var sw = new StringWriter();
        s.Serialize(sw, x);
        Console.WriteLine(sw);
    }
}

這給了你:

<?xml version="1.0" encoding="utf-16"?>
<MainClass 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  <RealFoo>
   <X>0</X>
  </RealFoo>
 </Foo>
</MainClass>

盡管避免了很多樣板,但這對於該類的用戶來說顯然更麻煩。

一個愉快的媒介可能是將 XmlAnything 想法合並到第一種技術的“支持”屬性中。 通過這種方式,大部分繁重的工作都為您完成了,但該類的消費者除了與內省混淆之外不會受到任何影響。

解決方案是使用 DataContractSerializer 的反射。 您甚至不必用 [DataContract] 或 [DataMember] 標記您的課程。 它將任何對象序列化,無論它是否具有接口類型屬性(包括字典)到 xml。 這是一個簡單的擴展方法,它將任何對象序列化為 XML,即使它具有接口(注意,您也可以調整它以遞歸運行)。

    public static XElement ToXML(this object o)
    {
        Type t = o.GetType();

        Type[] extraTypes = t.GetProperties()
            .Where(p => p.PropertyType.IsInterface)
            .Select(p => p.GetValue(o, null).GetType())
            .ToArray();

        DataContractSerializer serializer = new DataContractSerializer(t, extraTypes);
        StringWriter sw = new StringWriter();
        XmlTextWriter xw = new XmlTextWriter(sw);
        serializer.WriteObject(xw, o);
        return XElement.Parse(sw.ToString());
    }

LINQ 表達式的作用是枚舉每個屬性,返回作為接口的每個屬性,獲取該屬性(基礎對象)的值,獲取該具體對象的類型,將其放入數組中,並將其添加到序列化程序的已知類型的列表。

現在序列化程序知道它正在序列化的類型如何,因此它可以完成它的工作。

如果您預先知道您的接口實現者,那么您可以使用一個相當簡單的技巧來讓您的接口類型序列化,而無需編寫任何解析代碼:

public interface IInterface {}
public class KnownImplementor01 : IInterface {}
public class KnownImplementor02 : IInterface {}
public class KnownImplementor03 : IInterface {}
public class ToSerialize {
  [XmlIgnore]
  public IInterface InterfaceProperty { get; set; }
  [XmlArray("interface")]
  [XmlArrayItem("ofTypeKnownImplementor01", typeof(KnownImplementor01))]
  [XmlArrayItem("ofTypeKnownImplementor02", typeof(KnownImplementor02))]
  [XmlArrayItem("ofTypeKnownImplementor03", typeof(KnownImplementor03))]
  public object[] InterfacePropertySerialization {
    get { return new[] { InterfaceProperty }; ; }
    set { InterfaceProperty = (IInterface)value.Single(); }
  }
}

生成的 xml 看起來應該類似於

 <interface><ofTypeKnownImplementor01><!-- etc... -->

您可以使用ExtendedXmlSerializer 這個序列化器支持接口屬性的序列化,沒有任何技巧。

var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create();

var obj = new Example
                {
                    Model = new Model { Name = "name" }
                };

var xml = serializer.Serialize(obj);

您的 xml 將如下所示:

<?xml version="1.0" encoding="utf-8"?>
<Example xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Simple;assembly=ExtendedXmlSerializer.Samples">
    <Model exs:type="Model">
        <Name>name</Name>
    </Model>
</Example>

ExtendedXmlSerializer 支持 .net 4.5 和 .net Core。

用抽象或具體類型替換 IModelObject 接口並使用 XMLInclude 繼承是可能的,但似乎是一個丑陋的解決方法。

如果可以使用抽象基礎,我會推薦這條路線。 它仍然比使用手動序列化更干凈。 我看到抽象基礎的唯一問題是您仍然需要具體類型嗎? 至少這是我過去使用它的方式,例如:

public abstract class IHaveSomething
{
    public abstract string Something { get; set; }
}

public class MySomething : IHaveSomething
{
    string _sometext;
    public override string Something 
    { get { return _sometext; } set { _sometext = value; } }
}

[XmlRoot("abc")]
public class seriaized
{
    [XmlElement("item", typeof(MySomething))]
    public IHaveSomething data;
}

不幸的是,沒有簡單的答案,因為序列化程序不知道要為接口序列化什么。 我在MSDN上找到了關於如何解決這個問題的更完整的解釋

對我來說不幸的是,我有一個案例,要序列化的類的屬性也有接口作為屬性,所以我需要遞歸處理每個屬性。 此外,一些接口屬性被標記為 [XmlIgnore],所以我想跳過這些。 我接受了在這個線程上找到的想法,並在其中添加了一些東西以使其具有遞歸性。 此處僅顯示反序列化代碼:

void main()
{
    var serializer = GetDataContractSerializer<MyObjectWithCascadingInterfaces>();
    using (FileStream stream = new FileStream(xmlPath, FileMode.Open))
    {
        XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
        var obj = (MyObjectWithCascadingInterfaces)serializer.ReadObject(reader);

        // your code here
    }
}

DataContractSerializer GetDataContractSerializer<T>() where T : new()
{
    Type[] types = GetTypesForInterfaces<T>();

    // Filter out duplicates
    Type[] result = types.ToList().Distinct().ToList().ToArray();

    var obj = new T();
    return new DataContractSerializer(obj.GetType(), types);
}

Type[] GetTypesForInterfaces<T>() where T : new()
{
    return GetTypesForInterfaces(typeof(T));
}

Type[] GetTypesForInterfaces(Type T)
{
    Type[] result = new Type[0];
    var obj = Activator.CreateInstance(T);

    // get the type for all interface properties that are not marked as "XmlIgnore"
    Type[] types = T.GetProperties()
        .Where(p => p.PropertyType.IsInterface && 
            !p.GetCustomAttributes(typeof(System.Xml.Serialization.XmlIgnoreAttribute), false).Any())
        .Select(p => p.GetValue(obj, null).GetType())
        .ToArray();

    result = result.ToList().Concat(types.ToList()).ToArray();

    // do the same for each of the types identified
    foreach (Type t in types)
    {
        Type[] embeddedTypes = GetTypesForInterfaces(t);
        result = result.ToList().Concat(embeddedTypes.ToList()).ToArray();
    }
    return result;
}

我找到了一個更簡單的解決方案(您不需要 DataContractSerializer),這要歸功於這里的博客: XML serializing derived types when base type is in another namespace or DLL

但是在這個實現中可能會出現兩個問題:

(1)如果DerivedBase不在類Base的命名空間中,或者更糟糕的是在依賴Base命名空間的項目中,所以Base不能XMLInclude DerivedBase

(2) 如果我們只有類 Base 作為 dll ,那么 Base 不能 XMLInclude DerivedBase

直到現在, ...

所以解決這兩個問題的方法是使用XmlSerializer Constructor (Type, array[])

XmlSerializer ser = new XmlSerializer(typeof(A), new Type[]{ typeof(DerivedBase)});

MSDN 上提供了詳細示例:XmlSerializer Constructor (Type, extraTypesArray[])

在我看來,對於 DataContracts 或 Soap XML,您需要檢查此 SO question 中提到的 XmlRoot

SO上有一個類似的答案,但它沒有標記為一個,因為它不是OP似乎已經考慮過它。

在我的項目中,我有一個
列表<IFormatStyle> FormatStyleTemplates;
包含不同的類型。

然后我使用上面的解決方案“XmlAnything”來序列化這個不同類型的列表。 生成的xml很漂亮。

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlArray("FormatStyleTemplates")]
    [XmlArrayItem("FormatStyle")]
    public XmlAnything<IFormatStyle>[] FormatStyleTemplatesXML
    {
        get
        {
            return FormatStyleTemplates.Select(t => new XmlAnything<IFormatStyle>(t)).ToArray();
        }
        set
        {
            // read the values back into some new object or whatever
            m_FormatStyleTemplates = new FormatStyleProvider(null, true);
            value.ForEach(t => m_FormatStyleTemplates.Add(t.Value));
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM