5

我在 microsoft .net 3.5 (VS2008) 上使用 C# 3。我有反序列化的问题。我在希望可序列化的类层次结构中使用 DataContract 和 DataMember。

但是,我在一个容器中也有多态性,因此我需要将已知类型的列表传递给序列化程序。我的收藏是我在网上找到的可序列化字典:

[Serializable]
[XmlRoot("dictionary")]
public class SerializableSortedDictionary<TKey, TVal>
    : SortedDictionary<TKey, TVal>, IXmlSerializable
{
    #region Constants
    private const string DictionaryNodeName = "Dictionary";
    private const string ItemNodeName = "Item";
    private const string KeyNodeName = "Key";
    private const string ValueNodeName = "Value";
    #endregion

    #region Constructors
    public SerializableSortedDictionary()
    {
    }

    public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary)
    : base(dictionary)
    {
    }

    public SerializableSortedDictionary(IComparer<TKey> comparer)
    : base(comparer)
    {
    }

    public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary, IComparer<TKey> comparer)
    : base(dictionary, comparer)
    {
    }

    #endregion

    #region IXmlSerializable Members

    void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
    {
        //writer.WriteStartElement(DictionaryNodeName);
        foreach (KeyValuePair<TKey, TVal> kvp in this)
        {
            writer.WriteStartElement(ItemNodeName);
            writer.WriteStartElement(KeyNodeName);
            KeySerializer.Serialize(writer, kvp.Key);
            writer.WriteEndElement();
            writer.WriteStartElement(ValueNodeName);
            ValueSerializer.Serialize(writer, kvp.Value);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
        //writer.WriteEndElement();
    }

    void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
    {
        if (reader.IsEmptyElement)
        {
            return;
        }

        // Move past container
        if (!reader.Read())
        {
            throw new XmlException("Error in Deserialization of Dictionary");
        }

        //reader.ReadStartElement(DictionaryNodeName);
        while (reader.NodeType != XmlNodeType.EndElement)
        {
            reader.ReadStartElement(ItemNodeName);
            reader.ReadStartElement(KeyNodeName);
            TKey key = (TKey)KeySerializer.Deserialize(reader);
            reader.ReadEndElement();
            reader.ReadStartElement(ValueNodeName);
            TVal value = (TVal)ValueSerializer.Deserialize(reader);
            reader.ReadEndElement();
            reader.ReadEndElement();
            this.Add(key, value);
            reader.MoveToContent();
        }
        //reader.ReadEndElement();

        reader.ReadEndElement(); // Read End Element to close Read of containing node
    }

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

    // for serialization/deserialization pruporses
    public void SetKnownTypes(Type[] extraTypes)
    {
        this.extraTypes = extraTypes;
    }

    public Type[] extraTypes = null;

    #endregion

    #region Private Properties
    protected XmlSerializer ValueSerializer
    {
        get
        {
            if (valueSerializer == null)
            {
                if (extraTypes == null)
                    valueSerializer = new XmlSerializer(typeof(TVal));
                else
                    valueSerializer = new XmlSerializer(typeof(TVal), extraTypes);
            }
            return valueSerializer;
        }
    }

    private XmlSerializer KeySerializer
    {
        get
        {
            if (keySerializer == null)
            {
                if (extraTypes == null)
                    keySerializer = new XmlSerializer(typeof(TKey));
                else
                    keySerializer = new XmlSerializer(typeof(TKey), extraTypes);
            }
            return keySerializer;
        }
    }
    #endregion
    #region Private Members
    [NonSerialized]
    private XmlSerializer keySerializer = null;
    [NonSerialized]
    private XmlSerializer valueSerializer = null;
    #endregion
}

这是在其 TVal 中保存多态对象树的树。所以你看我已经修改了原始代码以添加一个已知类型的列表,这对于序列化很有效,因为我在我的高级类构造函数中设置了这个列表。(保存字典实例的类)。

这个已知类型的列表恰好在运行时被发现,使用这个函数:

    static public class TypeDiscoverer
    {
        public enum EFilter { All, OnlyConcreteTypes }
        public enum EAssemblyRange { AllAppDomain, OnlyAssemblyOfRequestedType }

        public static List<Type> FindAllDerivedTypes<T>(EFilter typesFilter, EAssemblyRange assembRange)
        {
            HashSet< Type > founds = new HashSet<Type>();

            Assembly[] searchDomain =
                assembRange == EAssemblyRange.OnlyAssemblyOfRequestedType ?
                new Assembly[1] { Assembly.GetAssembly(typeof(T)) }
                : AppDomain.CurrentDomain.GetAssemblies();

            foreach (Assembly a in searchDomain)
            {
                founds = new HashSet<Type>(founds.Concat(FindAllDerivedTypes<T>(a, typesFilter)));
            }
            return founds.ToList();
        }

        public static List<Type> FindAllDerivedTypes<T>(Assembly assembly, EFilter typesFilter)
        {
            var derivedType = typeof(T);

            List<Type> result = assembly
                                .GetTypes()
                                .Where(t =>
                                       t != derivedType &&
                                       derivedType.IsAssignableFrom(t)
                                      ).ToList();

            if (typesFilter == EFilter.OnlyConcreteTypes)
                result = result.Where(x => !x.IsAbstract).ToList();
            return result;
        }
    }

这个动态系统允许我通过只知道基类来发现已知类型。这是我一直想知道的为什么框架不提供这个功能......但是......

所以我的问题是,我的可序列化字典是一个实用程序类,我不能专门用它来硬编码已知类型的列表,更不用说因为它是在运行时发现的。 反序列化适用于未初始化的对象,因此我无法将已知类型的列表提供给字典反序列化器。

当然,目前,我将通过FindAllDerivedTypes直接在字典中使用我在 TVal 上的函数发现已知类型列表来解决这个问题。

但由于它的可扩展性不如外部提供的类型列表,我想知道是否有人可以为我提供真正的修复。

多谢。

4

2 回答 2

2

首先XmlSerializer忽略[Serializable], [NonSerialized]和属性 - 它由接口控制[DataContract](它完全改变对象序列化的行为)或默认情况下它序列化对象的所有公共成员/属性,您可以通过属性给出提示,如, , , , , ,等[DataMember]IXmlSerializableXmlSerializer[XmlRoot][XmlAttribute][XmlIgnore] [XmlArray][XmlElement][XmlArrayItem][XmlInclude][XmlText]

您所追求的功能已经包含在这些属性中。让我们假设你有SerializableSortedDictionary<string, Car>where Caris class with subclassesVolvoAudi.

[XmlInclude(typeof(Audi))]
[XmlInclude(typeof(Volvo))]
public class Car {
  private string m_Name = "Car";

  public virtual string Name {
    get { return m_Name; }
    set { m_Name = value; }
  }
}

public class Audi : Car {
  private string m_Name = "Audi";

  public override string Name {
    get { return m_Name; }
    set { m_Name = value; }
  }
}

public class Volvo : Car {
  private string m_Name = "Volvo";

  public override string Name {
    get { return m_Name; }
    set { m_Name = value; }
  }
}

您所需要的只是通过所有可能的子类来装饰基类XmlInclude

var dic = new SerializableSortedDictionary<string, Car>();
dic.Add("0", new Car());
dic.Add("1", new Audi());
dic.Add("2", new Volvo());

var serializer = new XmlSerializer(typeof(SerializableSortedDictionary<string, Car>));
var builder = new StringBuilder();
using(var writer = new StringWriter(builder)) {
  serializer.Serialize(writer, dic);
}

反序列化也有效。您可能会注意到生成的 xml 中的 xsi:type 属性——即 xml 序列化程序如何保留有关类型的信息。如果不指定它,基本上不可能从序列化对象中“猜测”类型。它不适用于未通过指定的泛型类型XmlInclude- 这是一项安全功能(如果攻击者可以在您解析 xml 提要时让您创建他喜欢的任何对象的实例,您可能会遇到严重的麻烦)。

于 2014-08-06T09:26:44.377 回答
2

您可以使用自定义 XmlReader(或传递一些静态/线程本地存储变量中的类型)。 IdeOne 示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace DynaXmlSer {

    public class KnownTypesXmlReader: XmlTextReader {
        public KnownTypesXmlReader(Stream ios): base(ios) {}
        public Type[] ExtraTypes = null;
    }

    public partial class SerializableSortedDictionary<TKey, TVal>
        : SortedDictionary<TKey, TVal>, IXmlSerializable
    {
        public void SetKnownTypes(Type[] extraTypes) {
            this.extraTypes = extraTypes;
            valueSerializer = null;
            keySerializer = null;
        }
        void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) {
            if (reader.IsEmptyElement)
                return;
            if (!reader.Read())
                throw new XmlException("Error in Deserialization of Dictionary");

            //HERE IS THE TRICK
            if (reader is KnownTypesXmlReader)
                SetKnownTypes(((KnownTypesXmlReader)reader).ExtraTypes);

            //reader.ReadStartElement(DictionaryNodeName);
            while (reader.NodeType != XmlNodeType.EndElement)
            {
                reader.ReadStartElement(ItemNodeName);
                reader.ReadStartElement(KeyNodeName);
                TKey key = (TKey)KeySerializer.Deserialize(reader);
                reader.ReadEndElement();
                reader.ReadStartElement(ValueNodeName);
                TVal value = (TVal)ValueSerializer.Deserialize(reader);
                reader.ReadEndElement();
                reader.ReadEndElement();
                this.Add(key, value);
                reader.MoveToContent();
            }
            //reader.ReadEndElement();

            reader.ReadEndElement(); // Read End Element to close Read of containing node
        }

    }

    public class BasicElement {
        private string name;
        public string Name {
            get { return name; }
            set { name = value; } }
    }
    public class ElementOne: BasicElement {
        private string one;
        public string One {
            get { return one; }
            set { one = value; }
        }
    }
    public class ElementTwo: BasicElement {
        private string two;
        public string Two {
            get { return two; }
            set { two = value; }
        }
    }

    public class Program {
        static void Main(string[] args) {
            Type[] extraTypes = new Type[] { typeof(ElementOne), typeof(ElementTwo) };
            SerializableSortedDictionary<string, BasicElement> dict = new SerializableSortedDictionary<string,BasicElement>();
            dict.SetKnownTypes(extraTypes);
            dict["foo"] = new ElementOne() { Name = "foo", One = "FOO" };
            dict["bar"] = new ElementTwo() { Name = "bar", Two = "BAR" };

            XmlSerializer ser = new XmlSerializer(typeof(SerializableSortedDictionary<string, BasicElement>));

            MemoryStream mem = new MemoryStream();
            ser.Serialize(mem, dict);
            Console.WriteLine(Encoding.UTF8.GetString(mem.ToArray()));
            mem.Position = 0;

            using(XmlReader rd = new KnownTypesXmlReader(mem) { ExtraTypes = extraTypes })
                dict = (SerializableSortedDictionary<string, BasicElement>)ser.Deserialize(rd);

            foreach(KeyValuePair<string, BasicElement> e in dict) {
                Console.Write("Key = {0}, Name = {1}", e.Key, e.Value.Name);
                if(e.Value is ElementOne) Console.Write(", One = {0}", ((ElementOne)e.Value).One);
                else if(e.Value is ElementTwo) Console.Write(", Two = {0}", ((ElementTwo)e.Value).Two);
                Console.WriteLine(", Type = {0}", e.Value.GetType().Name);
            }
        }
    }

    [Serializable]
    [XmlRoot("dictionary")]
    public partial class SerializableSortedDictionary<TKey, TVal>
        : SortedDictionary<TKey, TVal>, IXmlSerializable
    {
        #region Constants
        private const string DictionaryNodeName = "Dictionary";
        private const string ItemNodeName = "Item";
        private const string KeyNodeName = "Key";
        private const string ValueNodeName = "Value";
        #endregion

        #region Constructors
        public SerializableSortedDictionary()
        {
        }

        public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary)
        : base(dictionary)
        {
        }

        public SerializableSortedDictionary(IComparer<TKey> comparer)
        : base(comparer)
        {
        }

        public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary, IComparer<TKey> comparer)
        : base(dictionary, comparer)
        {
        }

        #endregion

        #region IXmlSerializable Members

        void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
        {
            //writer.WriteStartElement(DictionaryNodeName);
            foreach (KeyValuePair<TKey, TVal> kvp in this)
            {
                writer.WriteStartElement(ItemNodeName);
                writer.WriteStartElement(KeyNodeName);
                KeySerializer.Serialize(writer, kvp.Key);
                writer.WriteEndElement();
                writer.WriteStartElement(ValueNodeName);
                ValueSerializer.Serialize(writer, kvp.Value);
                writer.WriteEndElement();
                writer.WriteEndElement();
            }
            //writer.WriteEndElement();
        }

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


        public Type[] extraTypes = null;

        #endregion

        #region Private Properties
        protected XmlSerializer ValueSerializer
        {
            get
            {
                if (valueSerializer == null)
                {
                    if (extraTypes == null)
                        valueSerializer = new XmlSerializer(typeof(TVal));
                    else
                        valueSerializer = new XmlSerializer(typeof(TVal), extraTypes);
                }
                return valueSerializer;
            }
        }

        private XmlSerializer KeySerializer
        {
            get
            {
                if (keySerializer == null)
                {
                    if (extraTypes == null)
                        keySerializer = new XmlSerializer(typeof(TKey));
                    else
                        keySerializer = new XmlSerializer(typeof(TKey), extraTypes);
                }
                return keySerializer;
            }
        }
        #endregion
        #region Private Members
        [NonSerialized]
        private XmlSerializer keySerializer = null;
        [NonSerialized]
        private XmlSerializer valueSerializer = null;
        #endregion
    }

}

输出:

Key = bar,Name = bar,Two = BAR,Type = ElementTwo
键 = foo,名称 = foo,一 = FOO,类型 = ElementOne

您可以注释掉类型的传递和反序列化失败:using(XmlReader rd = new KnownTypesXmlReader(mem) /* { ExtraTypes = extraTypes } */)

添加评论:

我正在按以下顺序寻找解决方案:

  1. 如果可以,请使用已有的 ( [XmlInclude]) 。(您的运行时约束不允许这样做。)
  2. 定制一些涉及的类 - 问题出在void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)因此XmlReader是完美的候选人。我建议使用iterface最终解决方案(例如interface KnownTypes { Type[] GetKnownTypes(object me, string hint, params Type[] involved); }
  3. 如果上述失败(如果您无法控制反序列化器),请使用static变量(或 thread-local-storage 用于多线程,或static synchronized(weak)dictionary)进行额外配置。(不完美,似乎您可以使用选项 2。)
于 2014-08-06T18:24:52.137 回答