web-dev-qa-db-ja.com

インターフェイスプロパティのXMLシリアル化

(とりわけ)タイプIModelObject(インターフェイス)のプロパティを持つオブジェクトをXMLシリアル化したいと思います。

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

このクラスのオブジェクトをシリアル化しようとすると、次のエラーが表示されます。
「インターフェイスであるため、Example型のメンバーExample.Modelをシリアル化できません。」

問題は、インターフェイスをシリアル化できないことだと理解しています。ただし、具体的なModelオブジェクトタイプは実行時まで不明です。

IModelObjectインターフェイスを抽象型または具象型に置き換え、XMLIncludeで継承を使用することは可能ですが、見苦しい回避策のようです。

助言がありますか?

78
Elad

これは、型情報が出力内に埋め込まれていない宣言型シリアル化に固有の制限です。

<Flibble Foo="10" />を再び変換しようとすると

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

シリアライザーは、int、string、double(または何か)である必要があるかどうかをどのように知るのですか?.

これを機能させるにはいくつかのオプションがありますが、実行時まで本当に分からない場合、これを行う最も簡単な方法は XmlAttributeOverrides を使用することです。

悲しいことに、これは基本クラスでのみ機能し、インターフェイスでは機能しません。ここでできる最善の方法は、ニーズに十分ではないプロパティを無視することです。

本当にインターフェースにとどまらなければならない場合、3つの本当のオプションがあります:

非表示にして別のプロパティで処理する

Uい、不快なボイラープレートと多くの繰り返しがクラスのほとんどの消費者は問題に対処する必要はありません。

[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のアイデアを最初の手法の「バッキング」プロパティにマージしている可能性があります。このようにして、大雑把な作業の大部分はあなたのために行われますが、クラスの消費者は内省との混乱を超えて影響を受けません。

111
ShuggyCoUk

これに対する解決策は、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式が行うことは、各プロパティを列挙し、インターフェイスである各プロパティを返し、そのプロパティ(基になるオブジェクト)の値を取得し、その具象オブジェクトの型を取得して配列に入れ、それをシリアライザに追加します既知のタイプのリスト。

これで、シリアライザーは、自分の仕事を実行できるように、シリアライズしている型について知っています。

40
Despertar

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をサポートしています。

7
Wojtpl2

インターフェイスの実装者が事前にわかっている場合は、解析コードを作成せずにインターフェイスタイプをシリアル化するために使用できるかなり単純なハックがあります。

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... -->
5
hannasm

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;
}
3
csharptest.net

残念ながら、シリアライザーはインターフェイス用に何をシリアル化するのかわからないため、単純な答えはありません。 [〜#〜] msdn [〜#〜] でこれを回避する方法について、より完全な説明を見つけました。

2
MattH

残念なことに、シリアル化されるクラスにプロパティとしてインターフェイスを持つプロパティがある場合があったため、各プロパティを再帰的に処理する必要がありました。また、一部のインターフェイスプロパティは[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;
}
1
acordner

私のプロジェクトでは、
List <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));
        }
    }
0
Detlef Kroll

ここにあるこのブログのおかげで、より簡単なソリューションが見つかりました(DataContractSerializerは必要ありません)。 ベース型が別のネームスペースまたはDLL にある場合の派生型のXMLシリアル化

ただし、この実装では2つの問題が発生する可能性があります。

(1)DerivedBaseがクラスBaseのネームスペースにない場合、またはBaseネームスペースに依存するプロジェクトでさらに悪い場合、BaseがXMLInclude DerivedBaseをできない場合

(2)クラスBaseのみをdllとして持っているので、BaseがXMLInclude DerivedBaseをできない場合

今まで、...

したがって、2つの問題の解決策は、XmlSerializerコンストラクター(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が既にそれを考慮していないようであるため、1としてマークされていません。

0
B Charles H