11

基本プロジェクトには、抽象基本クラス Foo が含まれています。別のクライアント プロジェクトには、その基本クラスを実装するクラスがあります。

基本クラスでいくつかのメソッドを呼び出して、具体的なクラスのインスタンスをシリアル化および復元したいと思います。

// In the base project:
public abstract class Foo
{
    abstract void Save (string path);
    abstract Foo Load (string path);
}

逆シリアル化の時点で、必要なすべてのクラスが存在すると想定できます。何らかの方法で可能であれば、シリアル化は XML で行う必要があります。基本クラスに IXmlSerializable を実装させることは可能です。

ここでちょっと立ち往生しています。私の理解が正しければ、これは[XmlInclude(typeof(UnknownClass))]すべての実装クラスの基本クラスに を追加することによってのみ可能ですが、実装クラスは不明です!

これを行う方法はありますか?リフレクションの経験はありませんが、リフレクションを使用した回答も歓迎します。

編集:問題はシリアライズです。シリアル化するだけで簡単になります。:-)

4

9 に答える 9

9

XmlSerializerコンストラクターで追加の詳細を提供することにより、を作成する時点でこれを行うこともできます。このようなモデルは再利用されないことに注意してください。そのため、(アプリの起動時、構成から) 1 回構成し、繰り返し再利用することをお勧めします。オーバーロードXmlSerializerを使用すると、さらに多くのカスタマイズが可能であることに注意してください。 XmlAttributeOverrides.

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
static class Program
{
    static readonly XmlSerializer ser;
    static Program()
    {
        List<Type> extraTypes = new List<Type>();
        // TODO: read config, or use reflection to
        // look at all assemblies
        extraTypes.Add(typeof(Bar));
        ser = new XmlSerializer(typeof(Foo), extraTypes.ToArray());
    }
    static void Main()
    {
        Foo foo = new Bar();
        MemoryStream ms = new MemoryStream();
        ser.Serialize(ms, foo);
        ms.Position = 0;
        Foo clone = (Foo)ser.Deserialize(ms);
        Console.WriteLine(clone.GetType());
    }
}

public abstract class Foo { }
public class Bar : Foo {}
于 2009-02-26T14:12:52.703 に答える
3

シリアル化関数を基本クラスに配置する必要はありません。代わりに、ユーティリティ クラスに追加できます。

例 (コードは例にすぎず、rootName はオプションです)

public static class Utility
{
       public static void ToXml<T>(T src, string rootName, string fileName) where T : class, new()
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
            XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.UTF8);
            serializer.Serialize(writer, src);
            writer.Flush();
            writer.Close();
        }
}

電話をかけるだけで

Utility.ToXml( fooObj, "Foo", @"c:\foo.xml");

Foo のファミリ タイプだけでなく、他のすべてのシリアライズ可能なオブジェクトが使用できます。

編集

OK フルサービス... (rootName はオプションです)

public static T FromXml<T>(T src, string rootName, string fileName) where T : class, new()
{
    XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
    TextReader reader = new StreamReader(fileName);
    return serializer.Deserialize(reader) as T;
}
于 2009-02-26T14:12:16.277 に答える
2

XmlSerializer コンストラクターは Type 引数を取ります。抽象ベースのメソッドを介して派生クラスのインスタンスで GetType を呼び出しても、派生型の実際の Type が返されます。したがって、本質的に、デシリアライズ時に適切なタイプを知っている限り、適切なタイプのシリアライズは簡単です。したがって、serialize と呼ばれるベースにメソッドを実装するかthis.GetType()、XmlSerializer のコンストラクターに渡すものを実装するか、現在の参照を渡し、serialize メソッドに処理させれば問題ありません。

編集:OP編集の更新..

逆シリアル化で型がわからない場合は、実際には文字列またはバイト配列しかありません。どこかにある種の識別子がなければ、クリークのようなものです。xx 基本クラスのすべての既知の派生型として逆シリアル化を試みるなど、できることがいくつかありますが、これはお勧めしません。

あなたの他のオプションは、手動で XML をたどり、型をプロパティとして埋め込むことによってオブジェクトを再構築することです。おそらくそれが記事で最初に意図したことですが、現状ではタイプを指定せずにこれを処理する組み込みのシリアル化。

于 2009-02-26T14:10:38.667 に答える
1

XML 名前空間の奥深くに、XmlReflectionImporter という素晴らしいクラスがあります。

これは、実行時にスキーマを作成する必要がある場合に役立ちます。

于 2009-02-26T14:04:56.450 に答える
1

これは、可能なすべての型で XmlSerializer passign をコンストラクターに作成することによっても実行できます。このコンストラクターを使用すると、xmlSerializer が毎回コンパイルされ、常に再作成するとリークが発生することに注意してください。単一のシリアライザーを作成し、それをアプリケーションで再利用する必要があります。

次に、シリアライザーをブートストラップし、リフレクションを使用して foo の子孫を探すことができます。

于 2009-02-26T14:10:35.860 に答える
0

未知の (しかし予想される) クラスの XmlType 属性を使用して、逆シリアル化の Type を決定しました。予想される型は、AbstractXmlSerializer クラスのインスタンス化中に読み込まれ、ディクショナリに配置されます。逆シリアル化中にルート要素が読み取られ、これにより型がディクショナリから取得されます。その後、通常どおりデシリアライズできます。

XmlMessage.class:

public abstract class XmlMessage
{
}

IdleMessage.class:

[XmlType("idle")]
public class IdleMessage : XmlMessage
{
    [XmlElement(ElementName = "id", IsNullable = true)]
    public string MessageId
    {
        get;
        set;
    }
}

AbstractXmlSerializer.class:

public class AbstractXmlSerializer<AbstractType> where AbstractType : class
{
    private Dictionary<String, Type> typeMap;

    public AbstractXmlSerializer(List<Type> types)
    {            
        typeMap = new Dictionary<string, Type>();

        foreach (Type type in types)
        {
            if (type.IsSubclassOf(typeof(AbstractType))) {
                object[] attributes = type.GetCustomAttributes(typeof(XmlTypeAttribute), false);

                if (attributes != null && attributes.Count() > 0)
                {
                    XmlTypeAttribute attribute = attributes[0] as XmlTypeAttribute;
                    typeMap[attribute.TypeName] = type;
                }
            }
        }
    }

    public AbstractType Deserialize(String xmlData)
    {
        if (string.IsNullOrEmpty(xmlData))
        {
            throw new ArgumentException("xmlData parameter must contain xml");
        }            

        // Read the Data, Deserializing based on the (now known) concrete type.
        using (StringReader stringReader = new StringReader(xmlData))
        {
            using (XmlReader xmlReader = XmlReader.Create(stringReader))
            {
                String targetType = GetRootElementName(xmlReader);

                if (targetType == null)
                {
                    throw new InvalidOperationException("XML root element was not found");
                }                        

                AbstractType result = (AbstractType)new
                    XmlSerializer(typeMap[targetType]).Deserialize(xmlReader);
                return result;
            }
        }
    }

    private static string GetRootElementName(XmlReader xmlReader)
    {            
        if (xmlReader.IsStartElement())
        {
            return xmlReader.Name;
        }

        return null;
    }
}

単体テスト:

[TestMethod]
public void TestMethod1()
{
    List<Type> extraTypes = new List<Type>();
    extraTypes.Add(typeof(IdleMessage));
    AbstractXmlSerializer<XmlMessage> ser = new AbstractXmlSerializer<XmlMessage>(extraTypes);

    String xmlMsg = "<idle></idle>";

    MutcMessage result = ser.Deserialize(xmlMsg);
    Assert.IsTrue(result is IdleMessage);           
}
于 2016-12-08T08:51:40.843 に答える
0

このメソッドは、XML ルート要素を読み取り、現在実行中のアセンブリにそのような名前の型が含まれているかどうかを確認します。その場合、XML ドキュメントは逆シリアル化されます。そうでない場合は、エラーがスローされます。

public static T FromXml<T>(string xmlString)
{
    Type sourceType;
    using (var stringReader = new StringReader(xmlString))
    {
        var rootNodeName = XElement.Load(stringReader).Name.LocalName;
        sourceType =
                Assembly.GetExecutingAssembly().GetTypes()
                    .FirstOrDefault(t => t.IsSubclassOf(typeof(T)) 
                                    && t.Name == rootNodeName)
                ??
                Assembly.GetAssembly(typeof(T)).GetTypes()
                    .FirstOrDefault(t => t.IsSubclassOf(typeof(T)) 
                                    && t.Name == rootNodeName);

        if (sourceType == null)
        {
            throw new Exception();
        }
    }

    using (var stringReader = new StringReader(xmlString))
    {
        if (sourceType.IsSubclassOf(typeof(T)) || sourceType == typeof(T))
        {
            var ser = new XmlSerializer(sourceType);

            using (var xmlReader = new XmlTextReader(stringReader))
            {
                T obj;
                obj = (T)ser.Deserialize(xmlReader);
                xmlReader.Close();
                return obj;
            }
        }
        else
        {
            throw new InvalidCastException(sourceType.FullName
                                           + " cannot be cast to "
                                           + typeof(T).FullName);
        }
    }
}
于 2011-12-06T17:03:11.750 に答える
0

クラスを Serializable としてマークし、 XmlSerializer の代わりにSoap BinaryFormatter を使用すると、この機能が自動的に提供されます。シリアル化すると、シリアル化されるインスタンスの型情報が XML に書き込まれ、Soap BinaryFormatter は、逆シリアル化するときにサブクラスをインスタンス化できます。

于 2009-02-26T14:17:27.153 に答える