web-dev-qa-db-ja.com

XmlSerializerを使用して、型を事前に知らなくても、基本クラスまたは派生クラスのオブジェクトを逆シリアル化するにはどうすればよいですか?

C#で、XmlSerializerを使用して、基本クラスまたはいくつかの派生クラスのいずれかである可能性のあるオブジェクトを、事前に型を知らずに逆シリアル化するにはどうすればよいですか?

私の派生クラスはすべて、データメンバーを追加します。クラスオブジェクトをシリアル化および逆シリアル化できる簡単なGUIを作成しました。ユーザーが入力することを選択したフィールドに基づいて、継承されたクラス(または基本クラスのみ)が適切であるようにオブジェクトをシリアル化します。

シリアル化に問題はありません。問題は逆シリアル化です。事前にクラスを知らなくても、XmlSerializerでデータを正しい派生クラスに逆シリアル化するにはどうすればよいですか?現在、XMLファイルの最初のノードを読み取ってそこからクラスを決定するためにXmlReaderを作成しています。これは私の目的には機能するようですが、非常に洗練されていないソリューションのようです。

以下にサンプルコードをいくつか投稿しました。助言がありますか?

BaseType objectOfConcern = new BaseType();
XmlSerializer xserializer;
XmlTextReader xtextreader = new XmlTextReader(DEFAULT_FILENAME);

do { xtextreader.Read(); } while (xtextreader.NodeType != XmlNodeType.Element);

string objectType = xtextreader.Name;
xtextreader.Close();

FileStream fstream = new FileStream(DEFAULT_FILENAME, FileMode.Open);

switch (objectType)
    {
case "type1":
    xserializer = new XmlSerializer(typeof(DerivedType));

    objectOfConcern = (DerivedType)xserializer.Deserialize(fstream);

    //Load fields specific to that derived type here
    whatever = (objectOfConcern as DerivedType).NoOfstreamubordinates.ToString();

    case "xxx_1":
        //code here

    case "xxx_2":
        //code here

    case "xxx_n":
        //code here

        //and so forth

    case "BaseType":
    xserializer = new XmlSerializer(typeof(BaseType));
    AssignEventHandler(xserializer);
    objectOfConcern = (BaseType)xserializer.Deserialize(fstream);
}

//Assign all deserialized values from base class common to all derived classes here

//Close the FileStream
fstream.Close();
20
8bitcartridge

その派生型を含むルートクラス/タグはありますか?はいの場合、 XmlElementAttribute を使用して、タグ名を次のタイプにマップできます。

public class RootElementClass
{
    [XmlElement(ElementName = "Derived1", Type = typeof(Derived1BaseType))]
    [XmlElement(ElementName = "Derived2", Type = typeof(Derived2BaseType))]
    [XmlElement(ElementName = "Derived3", Type = typeof(Derived3BaseType))]
    public BaseType MyProperty { get; set; }
}

public class BaseType { }
public class Derived1BaseType : BaseType { }
public class Derived2BaseType : BaseType { }
public class Derived3BaseType : BaseType { }
18
SeeSharp

私は最近、基本クラスTおよびTの派生クラス用にこの汎用シリアライザー\デシリアライザーを作成しました。これまでのところ機能しているようです。

Type []配列は、TおよびT自体のすべての派生型を格納します。デシリアライザーはそれぞれを試行し、正しいものが見つかったときに戻ります。

/// <summary>
/// A generic serializer\deserializer
/// </summary>
/// <typeparam name="T"></typeparam>
public static class Serializer<T>
{
    /// <summary>
    /// serialize an instance to xml
    /// </summary>
    /// <param name="instance"> instance to serialize </param>
    /// <returns> instance as xml string </returns>
    public static string Serialize(T instance)
    {
        StringBuilder sb = new StringBuilder();
        XmlWriterSettings settings = new XmlWriterSettings();

        using (XmlWriter writer = XmlWriter.Create(sb, settings))
        {
            XmlSerializer serializer = new XmlSerializer(instance.GetType());
            serializer.Serialize(writer, instance);
        }

        return sb.ToString();
    }

    /// <summary>
    /// deserialize an xml into an instance
    /// </summary>
    /// <param name="xml"> xml string </param>
    /// <returns> instance </returns>
    public static T Deserialize(string xml)
    {
        using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
        {
            foreach (Type t in types)
            {
                XmlSerializer serializer = new XmlSerializer(t);
                if (serializer.CanDeserialize(reader))
                    return (T)serializer.Deserialize(reader);
            }
        }

        return default(T);
    }

    /// <summary>
    /// store all derived types of T:
    /// is used in deserialization
    /// </summary>
    private static Type[] types = AppDomain.CurrentDomain.GetAssemblies()
                                        .SelectMany(s => s.GetTypes())
                                        .Where(t => typeof(T).IsAssignableFrom(t)
                                            && t.IsClass
                                            && !t.IsGenericType)
                                            .ToArray();
}
5
Bartel

コンストラクター XmlSerializer(Type type、Type [] extraTypes) を使用して、関連するすべての型で機能するシリアライザーを作成することができます。

4
Jordão

XmlSerializerを使用するように設定されていない場合は、代わりに DataContractSerializerKnownType 属性を使用できます。

各サブクラスの親クラスにKnownType属性を追加するだけで、残りはDataContractSerializerが行います。

DataContractSerializerは、xmlにシリアル化するときに型情報を追加し、逆シリアル化するときにその型情報を使用して正しい型を作成します。

たとえば、次のコード:

[KnownType( typeof( C2 ) )]
[KnownType( typeof( C3 ) )]
public class C1 {public string P1 {get;set;}}
public class C2 :C1 {public string P2 {get;set;}}
public class C3 :C1 {public string P3 {get;set;}}

class Program
{
  static void Main(string[] args)
  {
    var c1 = new C1{ P1="c1"};
    var c2 = new C2{ P1="c1", P2="c2"};
    var c3 = new C3{ P1="c1", P3="c3"};

    var s = new DataContractSerializer( typeof( C1 ) );
    Test( c1, s );
    Test( c2, s );
    Test( c3, s );
  }

  static void Test( C1 objectToSerialize, DataContractSerializer serializer )
  {
    using ( var stream = new MemoryStream() )
    {
      serializer.WriteObject( stream, objectToSerialize );
      stream.WriteTo( Console.OpenStandardOutput() );
      stream.Position = 0;
      var deserialized = serializer.ReadObject( stream );
      Console.WriteLine( Environment.NewLine + "Deserialized Type: " + deserialized.GetType().FullName );              
    }
  }
}

出力します:

<C1 xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<P1>c1</P1></C1>

Deserialized Type: ConsoleApplication1.C1

<C1 i:type="C2" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<P1>c1</P1><P2>c2</P2></C1>

Deserialized Type: ConsoleApplication1.C2

<C1 i:type="C3" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<P1>c1</P1><P3>c3</P3></C1>

Deserialized Type: ConsoleApplication1.C3

出力では、c2とc3のxmlに、DataContractSerializer.ReadObjectが正しい型を作成できるようにする追加の型情報が含まれていることがわかります。

4
Aaron Carlson

xmlIncludeを使用できます

[XmlInclude(typeof(MyClass))]
public abstract class MyBaseClass
{
   //...
}

それ以外の場合、シリアル化時にタイプを追加する場合:

Type[] types = new Type[]{ typeof(MyClass) }

XmlSerializer serializer = new XmlSerializer(typeof(MyBaseClass), types);
2
lupok