2

カスタムシリアライザーが必要なクラスがあります。また、このクラスをシリアル化したいリスト内で使用することもあります。これらの要素の一部は null になります。

シリアル化を機能させることができます:

<MyData xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Fractions>
    <Frac>1/2</Frac>
    <Frac xsi:nil="true" />
    <Frac xsi:nil="true" />
    <Frac>3/6</Frac>
  </Fractions>
</MyData>

ただし、上記のように null 要素が存在する場合、逆シリアル化は機能しません。List<> シリアライザーは、要素に対して ReadXml() を呼び出しているように見えますが、リストに null 要素を作成しているだけです。

私の例を実行すると、逆シリアル化されたバージョンは次のようになります。

1/2
/
/
3/6

つまり、要素 1 と 2 で null の代わりに MyFrac オブジェクトが作成されました。

これを回避するには、List サブクラスのカスタム シリアライザーを作成する必要がありますか?それとも、デシリアライズ時に null 要素を取得する他の方法がありませんか? カスタムシリアライザーの場合、最善のアプローチ/コードはありますか?

現在の実装を示す完全な例を以下に示します。

public class MyFrac : IXmlSerializable
{
    public string N;
    public string D;

    public override string ToString()
    {
        return N + "/" + D;
    }

    System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
    {
        return null;
    }

    void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
    {
        if (reader.IsEmptyElement && reader.NodeType != XmlNodeType.EndElement)
        {
            reader.Read();
            return;
        }

        reader.ReadStartElement();

        string sfrac = reader.ReadString();
        try
        {
            var m = Regex.Match(sfrac, @"(\d+)/(\d+)");
            if (!m.Success)
                throw new Exception(sfrac + " was not in the correct format");
            N = m.Result("$1");
            D = m.Result("$2");
        }
        finally
        {
            reader.ReadEndElement();
        }
    }

    void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteString(N + "/" + D);
    }
}

public class MyData
{
    [XmlArrayItem("Frac")]
    public List<MyFrac> Fractions;
}

public static void Run()
{
    var data = new MyData();
    data.Fractions = new List<MyFrac>();
    data.Fractions.Add(new MyFrac { N = "1", D = "2" });
    data.Fractions.Add(null);
    data.Fractions.Add(null);
    data.Fractions.Add(new MyFrac { N = "3", D = "6" });

    var serializer = new XmlSerializer(typeof(MyData));

    StringBuilder sb = new StringBuilder();

    using (var writer = new StringWriter(sb))
    {
        serializer.Serialize(writer, data);
    }

    // Dump XML
    Console.WriteLine(sb.ToString());

    using (var reader = new StringReader(sb.ToString()))
    {
        var data2 = (MyData)serializer.Deserialize(reader);
        Console.WriteLine(data2.Fractions[0]);
        Console.WriteLine(data2.Fractions[1]);
        Console.WriteLine(data2.Fractions[2]);
        Console.WriteLine(data2.Fractions[3]);
    }
}
4

1 に答える 1

2

逆シリアル化中に XmlSerializer が最初にデフォルトのコンストラクターを呼び出してオブジェクトを作成し、次にReadXmlメソッドを呼び出してプロパティ値を設定するため、ReadXmlがオブジェクトの作成をキャンセルできないために問題が発生します。null値をシリアライズしてxmlで表示する必要がありますか? リスト以外のコレクションを使用することで、これを回避できるということです。たとえば、カスタム コレクションを作成します。

public class MyCollection : System.Collections.ObjectModel.Collection<MyFrac>
{
    protected override void InsertItem(int index, MyFrac item)
    {
        if(item == null) return;
        base.InsertItem(index, item);
    }       

    protected override void SetItem(int index, MyFrac item)
    {
        if(item == null) 
        {
            base.RemoveAt(index);
        }
        else
        {
            base.SetItem(index, item);
        }
    }
}

MyDataクラスで使用します。

public class MyData
{
    [XmlArrayItem("Frac")]
    public MyCollection Fractions;
}

そして、シリアライゼーション/デシリアライゼーションは必要に応じて機能します:

    public static void Main(string[] args)
    {
        var data = new MyData();
        data.Fractions = new MyCollection();
        data.Fractions.Add(new MyFrac { N = "1", D = "2" });
        data.Fractions.Add(null);
        data.Fractions.Add(null);
        data.Fractions.Add(new MyFrac { N = "3", D = "6" });

        var serializer = new XmlSerializer(typeof(MyData));
        StringBuilder sb = new StringBuilder();

        using (var writer = new StringWriter(sb))
        {
            serializer.Serialize(writer, data);
        }

        // Dump XML
        Console.WriteLine(sb.ToString());

        using (var reader = new StringReader(sb.ToString()))
        {
            var data2 = (MyData)serializer.Deserialize(reader);
            foreach (var element in data2.Fractions) {
                Console.WriteLine(element);
            }
        }

        Console.ReadLine();
    }

シリアル化された xml:

<?xml version="1.0" encoding="utf-16"?>
<MyData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Fractions>
    <Frac>1/2</Frac>
    <Frac>3/6</Frac>
  </Fractions>
</MyData>

出力:

1/2

3/6

アップデート

わかりました。カスタムのシリアライゼーション ルールを含むコレクションが必要です。実装しましょう:

public class MyCollection<T> : Collection<T>, IXmlSerializable where T: class 
{
    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        var serializer = new XmlSerializer(typeof(T));
        var wasEmpty = reader.IsEmptyElement;
        reader.Read();
        if (wasEmpty)
            return;
        while (reader.NodeType != XmlNodeType.EndElement)
        {
            if (reader.IsEmptyElement)
            {
                reader.Read();
                Items.Add(null);
                continue;
            }
            var item = (T)serializer.Deserialize(reader);
            Items.Add(item);
        }
        reader.ReadEndElement();
    }

    public void WriteXml(XmlWriter writer)
    {
        var serializer = new XmlSerializer(typeof (T));
        foreach (var myFrac in Items)
        {
            serializer.Serialize(writer, myFrac);
        }
    }
}

使用法:

public class MyData
{
    public MyCollection<MyFrac> Fractions;
}

class Program
{
    static void Main(string[] args)
    {
        var data = new MyData();
        data.Fractions = new MyCollection<MyFrac>();
        data.Fractions.Add(new MyFrac { N = "1", D = "2" });
        data.Fractions.Add(null);
        data.Fractions.Add(null);
        data.Fractions.Add(new MyFrac { N = "3", D = "6" });

        var serializer = new XmlSerializer(typeof(MyData));

        StringBuilder sb = new StringBuilder();

        using (var writer = new StringWriter(sb))
        {
            serializer.Serialize(writer, data);
        }

        // Dump XML
        Console.WriteLine(sb.ToString());
        Trace.WriteLine(sb.ToString());

        using (var reader = new StringReader(sb.ToString()))
        {
            var data2 = (MyData)serializer.Deserialize(reader);
            foreach (var fraction in data2.Fractions)
            {
                var output = fraction == null ? "null" : fraction.ToString();
                Console.WriteLine(output);
                Trace.WriteLine(output);
            }
        }
        Console.ReadLine();
    }
}

出力 XML:

<?xml version="1.0" encoding="utf-16"?>
<MyData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Fractions>
    <MyFrac>1/2</MyFrac>
    <MyFrac xsi:nil="true" />
    <MyFrac xsi:nil="true" />
    <MyFrac>3/6</MyFrac>
  </Fractions>
</MyData>

出力データ:

1/2

ヌル

ヌル

3/6

これがあなたの望むものだと思います。

于 2013-10-17T07:10:09.690 に答える