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


83

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

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

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

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

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

助言がありますか?

回答:


116

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

変換しようとしてオン<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構造と直接対話できます
  • 短所
    • クラスの他のすべてのプロパティのホイールを再実装する必要がある場合があります

努力の重複の問題は最初のものと同様です。

折り返しタイプを使用するようにプロパティを変更します

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のアイデアを最初の手法の「バッキング」プロパティにマージすることかもしれません。このようにして、うなり声の仕事のほとんどはあなたのために行われますが、クラスの消費者は内省との混乱を超えて影響を受けることはありません。


私はあなたのアプローチの魔女ラッピングプロパティを実装しようとしましたが、残念ながら問題を抱えている:(あなたはこの記事を見てもらえ、してください:stackoverflow.com/questions/7584922/...
SOReader

FooSerializedプロパティを紹介する芸術的なものはありますか?
gqqnbig 2013

42

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

これで、シリアライザーは、シリアル化するタイプについて認識しているため、ジョブを実行できます。


問題に対する非常にエレガントで簡単な解決策。ありがとう!
ghlouw 2012年

2
これは、一般的なIListおよびインターフェイスでは機能しないようです。例:IList <IMyInterface>。IMyInterfaceのconcreate値をKnownTypesに追加する必要がありますが、代わりにIList <IMyInterface>が追加されます。
galford13x 2013年

6
@ galford13x要点を示しながら、この例をできるだけ単純にしようとしました。再帰やインターフェースタイプのように、これまでに1つのケースを追加すると、読みにくくなり、要点が失われます。必要な既知のタイプをプルするために、チェックを追加してください。正直なところ、リフレクションでは得られないものはないと思います。たとえば、これは汎用パラメータのタイプ、stackoverflow.com
Despertar

質問がインターフェースのシリアル化を求めていたので、私はこれについてのみ言及したことを理解しています。私は他の人に、彼らの側でヘッドバンギングを防ぐために修正なしでエラーが予想されることを知らせたいと思いました。ただし、[KnownType()]属性を追加したところ、コードから結果が得られたので、コードに感謝しました。
galford13x 2013年

1
シリアル化するときにnamesapceを省略する方法はありますか?代わりにxmlwriterを使用してxmlwriterSettingsを使用しようとしましたが、追加の型を渡すことができるオーバーロードを使用しましたが、機能していません...
Legends

9

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

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

1
とても便利です、ありがとう。ほとんどの場合、私はインターフェースを実装するクラスを知っています。この答えは、より高いimoでなければなりません。
ジョナ

これが最も簡単な解決策です。ありがとうございました!
mKay

8

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は.net4.5および.netCoreをサポートします。


3

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

2

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


1

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

このブログのおかげで、より簡単な解決策を見つけました(DataContractSerializerは必要ありません): 基本型が別の名前空間またはDLLにある場合の派生型のXMLシリアル化

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

(1)DerivedBaseがクラスBaseの名前空間にない場合、またはBase名前空間に依存するプロジェクトでさらに悪い場合、BaseがXMLIncludeDerivedBaseを実行できない場合はどうなりますか

(2)dllとしてクラスBaseしかない場合、BaseはXMLIncludeDerivedBaseを使用できません。

今まで、..。

したがって、2つの問題の解決策は、XmlSerializerコンストラクター(Type、array [])を使用することです。

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

詳細な例は、MSDNで提供されています: XmlSerializerコンストラクター(Type、extraTypesArray [])

DataContractsまたはSoapXMLの場合、このSOの質問で説明されているようにXmlRoot確認する必要があるように思われます。

同様の答えはここにSOであるが、ないOPはすでにそれを考えられているようだとして、それは、1としてマークされていません。


0

私のプロジェクトには、
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));
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.