14

私が持っているのは、を使用してシリアル化/逆シリアル化するカスタム設定ファイルですXmlSerializer。オブジェクト定義にスキーマが定義されておらず、シリアル化タグもありません。オブジェクトのシリアル化だけです (ただし、必要に応じて追加します)。

私の問題は、オブジェクトにデータ メンバーを追加する必要があることです。これを行うと、古い設定ファイルが逆シリアル化されないことがわかります。

追加されたメンバーのデフォルト値を指定する方法、または XML に欠落している場合にそれらを無視する簡単な方法はありますか?

4

7 に答える 7

7

MSDNから

ベスト プラクティス適切なバージョン管理動作を確保するには、型をバージョン間で変更する際に次の規則に従います。

  • 新しいシリアル化されたフィールドを追加するときは、OptionalFieldAttribute 属性を適用します。

  • (以前のバージョンではシリアル化できなかった) フィールドから NonSerializedAttribute 属性を削除する場合は、OptionalFieldAttribute 属性を適用します。

  • デフォルトとして 0 または null が受け入れられない限り、すべてのオプション フィールドに対して、シリアライゼーション コールバックを使用して意味のあるデフォルトを設定します。

クラスの新しいバージョンにElement2という名前の新しいメンバーがある場合をシミュレートしようとしました。私の新しいメンバーを「これは新しいメンバーです」に初期化しました ここに完全な証拠があります

Test1は、Element1 が 1 つのみの Root クラスの古い定義でシリアライズしたことを前提としています。

ルート クラスの新しい定義でシリアライズおよびデシリアライズしたときのTest2

デフォルト値を提供する方法で質問に答えるには、「OptionalField」を使用する必要があります

using System;
using System.Runtime.Serialization;
using System.IO;

public class Test
{

  [Serializable]
  public class Root
  {
    [OptionalField(VersionAdded = 2)] // As recommended by Microsoft
    private string mElement2 = "This is new member";
    public String Element1 { get; set; }    
    public String Element2 { get { return mElement2; } set { mElement2 = value; } }
  }

  public static void Main(string[] s)
  {
    Console.WriteLine("Testing serialized with old definition of Root ");
    Console.WriteLine(" ");
    Test_When_Original_Object_Was_Serialized_With_One_Element();
    Console.WriteLine(" ");
    Console.WriteLine("Testing serialized with new definition of Root ");
    Console.WriteLine(" ");
    Test_When_Original_Object_Was_Serialized_With_Two_Element();
    Console.ReadLine();
  }

  private static void TestReadingObjects(string xml)
  {
    System.Xml.Serialization.XmlSerializer xmlSerializer =
    new System.Xml.Serialization.XmlSerializer(typeof(Root));


    System.IO.Stream stream = new MemoryStream();
    System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
    Byte[] bytes = encoding.GetBytes(xml);
    stream.Write(bytes, 0, bytes.Length);
    stream.Position = 0;
    Root r = (Root)xmlSerializer.Deserialize(stream);

    Console.WriteLine(string.Format("Element 1 = {0}", r.Element1));

    Console.WriteLine(string.Format("Element 2 = {0}", r.Element2 == null ? "Null" : r.Element2));
  }
  private static void Test_When_Original_Object_Was_Serialized_With_One_Element()
  {
    TestReadingObjects(@"<Root>   <Element1>1</Element1>   </Root>");
  }

  private static void Test_When_Original_Object_Was_Serialized_With_Two_Element()
  {
    TestReadingObjects(@"<Root>   <Element1>1</Element1> <Element2>2</Element2>   </Root>");
  }
}

// ここに出力がありますここに画像の説明を入力

于 2011-11-17T23:19:39.487 に答える
5

デフォルトのコンストラクターを使用してアイテムを初期化するだけです。したがって、それらは変更されません。

于 2011-11-17T21:59:00.200 に答える
4

カスタムメソッドを使用して手動で処理し、適切な属性 OnSerializing/OnSerialized/OnDeserializing/OnDeserialized でマークし、値を初期化する方法を手動で決定する必要があります (可能な場合)。

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializingattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializedattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializedattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializingattribute.aspx

より良い方法は、事前にバージョンを決定し、戦略パターンを使用して逆シリアル化を行うことです。これは常に可能であるとは限らないため、その場合は私が提案するものを使用してください。

更新:前の回答は、バイナリのシリアル化にのみ適用されます。通常の Xml の場合、このメソッドを使用できます。

class Program
    {
        static void Main(string[] args)
        {
            Deserialize(@"..\..\v1.xml");
        }

        private static Model Deserialize(string file)
        {
            XDocument xdoc = XDocument.Load(file);
            var verAtt = xdoc.Root.Attribute(XName.Get("Version"));
            Model m = Deserialize<Model>(xdoc);

            IModelLoader loader = null;

            if (verAtt == null)
            {
                loader = GetLoader("1.0");
            }
            else
            {
                loader = GetLoader(verAtt.Value);
            }

            if (loader != null)
            {
                loader.Populate(ref m);
            }
            return m;
        }

        private static IModelLoader GetLoader(string version)
        {
            IModelLoader loader = null;
            switch (version)
            {
                case "1.0":
                    {
                        loader = new ModelLoaderV1();
                        break;
                    }
                case "2.0":
                    {
                        loader = new ModelLoaderV2();
                        break;
                    }
                case "3.0": { break; } //Current
                default: { throw new InvalidOperationException("Unhandled version"); }
            }
            return loader;
        }

        private static Model Deserialize<T>(XDocument doc) where T : Model
        {
            Model m = null;
            using (XmlReader xr = doc.CreateReader())
            {
               XmlSerializer xs = new XmlSerializer(typeof(T));
               m = (Model)xs.Deserialize(xr);
               xr.Close();
            }
            return m;
        }
    }

    public interface IModelLoader
    {
        void Populate(ref Model model);
    }

    public class ModelLoaderV1 : IModelLoader
    {
        public void Populate(ref Model model)
        {
            model.City = string.Empty;
            model.Phone = "(000)-000-0000";
        }
    }

    public class ModelLoaderV2 : IModelLoader
    {
        public void Populate(ref Model model)
        {
            model.Phone = "(000)-000-0000";
        }
    }

    public class Model
    {
        [XmlAttribute(AttributeName = "Version")]
        public string Version { get { return "3.0"; } set { } }
        public string Name { get; set; } //V1, V2, V3
        public string City { get; set; } //V2, V3
        public string Phone { get; set; } //V3 only

    }

要件によっては、これをモデル (またはモデル ローダー) の単一のメソッドに簡略化できます。

これは、逆シリアル化後のモデルの検証にも使用できます。

編集:次のコードを使用してシリアル化できます

 private static void Serialize(Model model)
        {
            XmlSerializer xs = new XmlSerializer(typeof(Model));
            FileStream f = File.Create(@"..\..\v1.xml");
            xs.Serialize(f, model);

            f.Close();
        }
于 2011-11-17T23:37:47.783 に答える
3

このパターンに従えば、かなり簡単です。

  • ISerializable を実装して、シリアル化/逆シリアル化を自分で処理する
  • これを使用して、オブジェクトのメンバーとシリアル化バージョン番号の両方をシリアル化します。
  • 逆シリアル化コードで、バージョン番号に対して switch-case ステートメントを実行します。開始時には、1 つのバージョン (最初の逆シリアル化コード) しかありません。先に進むにつれて、新しいバージョン番号を新しくシリアル化されたスナップショットにスタンプします。
  • オブジェクトの将来のバージョンでは、常に既存の逆シリアル化コードをそのままにするか、名前を変更/リファクタリングするメンバーにマップするように変更し、主に新しいシリアル化バージョンの新しい case ステートメントを追加するだけです。

このようにして、以前のバージョンのアセンブリからシリアル化スナップショットが生成された場合でも、以前のデータを正常に逆シリアル化できます。

于 2011-11-17T23:33:42.507 に答える
2

を使用し[System.ComponentModel.DefaultValueAttribute]て、シリアル化のDefaultValuesを定義します。

MSDNの例:

private bool myVal=false;

[DefaultValue(false)]
 public bool MyProperty {
    get {
       return myVal;
    }
    set {
       myVal=value;
    }
 }

したがって、DeSerializeを実行し、プロパティが入力されていない場合は、defaultValueをValueとして使用し、古いXMLを使用して新しいオブジェクトを生成できます。

新しいバージョンでプロパティが削除された場合、これはXMLSerializationを介して問題なく実行されるはずです。(私の知る限りでは)

于 2011-11-18T07:42:44.213 に答える
1

ExtendedXmlSerializerを使用できます。このシリアライザは、古いバージョンの xml のデシリアライズをサポートしています。古いバージョンの xml を逆シリアル化する例を次に示します。

1 つのファイルから異なるバージョンのオブジェクトを読み取ることもできます。

于 2016-09-22T08:28:00.890 に答える
0

.NET は、シリアル化/逆シリアル化とバージョン管理に非常に多くの機能を提供します。

1) ユーザーの DataContract / DataMember 属性および DataContractSerializer

2)MSDNによると、これらの変更は壊れています

  • データ コントラクトの名前または名前空間の値を変更します。
  • DataMemberAttribute の Order プロパティを使用して、データ メンバーの順序を変更します。
  • データ メンバーの名前を変更します。
  • データ メンバーのデータ コントラクトを変更します。

3) 余分なフィールドを持つ型が欠落しているフィールドを持つ型に逆シリアル化される場合、余分な情報は無視されます。

4) フィールドが欠落しているタイプが余分なフィールドを持つタイプに逆シリアル化される場合、余分なフィールドはデフォルト値 (通常はゼロまたは null) のままになります。

5) バージョン管理に IExtensibleDataObject の使用を検討する

于 2011-11-18T00:21:56.567 に答える