1. ホーム
  2. c#

[解決済み] インターフェイスプロパティのXMLシリアライゼーション

2023-04-30 16:27:13

質問

オブジェクトをXMLシリアライズしたいのですが、そのオブジェクトは(特に)プロパティタイプが IModelObject (これはインターフェースです) を持つオブジェクトをXMLシリアライズしたいです。

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

このクラスのオブジェクトをシリアライズしようとすると、以下のエラーが発生します。

"Example型のメンバーExample.Modelはインターフェースであるため、シリアライズできません。

インターフェイスがシリアライズできないことが問題であることは理解しました。しかし,具体的な モデル オブジェクトの型は実行時まで不明です。

を置き換えて IModelObject インターフェースを抽象型または具象型に置き換え、XMLInclude で継承を使用することは可能ですが、醜い回避方法のように思われます。

何か提案はありますか?

どのように解決するのですか?

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

変換しようとする際に <Flibble Foo="10" /> に戻そうとすると

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

シリアライザは、int、string、double(または他の何か)であるべきかをどのように知るのでしょうか...。

これを動作させるには、いくつかの選択肢がありますが、実行時まで本当にわからないのであれば、最も簡単なのは XmlAttributeOverrides .

悲しいことに、これはインターフェースではなく、基底クラスでしか動作しません。 そこでできることは、あなたのニーズには十分でないプロパティを無視することです。

もし本当にインターフェイスのままでなければならないのであれば、3つの選択肢があります。

隠して、別のプロパティで処理する

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

[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 構造を直接操作することができ、柔軟性やバージョンアップが可能です。
  • 短所
    • クラスの他のすべてのプロパティのホイールを再実装しなければならないかもしれません。

努力の重複の問題は、1番目と同様です。

ラッピングタイプを使用するようにプロパティを変更する

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のアイデアを最初のテクニックの'backing'プロパティにマージすることでしょう。この方法では、大変な作業のほとんどはあなたのために行われますが、クラスの消費者は、introspectionで混乱する以上の影響を受けません。