在(反)序列化树中使用自己的XmlSerializer将已知类型的列表传递给对象

时间:2013-10-15 01:07:31

标签: c# constructor deserialization xmlserializer datacontract

我在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;
        }
    }

这个动态系统允许我通过了解基类来发现已知类型。我总是想知道为什么框架不能提供这个功能......但是...... [/ p>

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

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

但由于它的可扩展性低于内部提供的类型列表,我想知道是否有人可以为我提供真正的解决方案。

非常感谢。

2 个答案:

答案 0 :(得分:2)

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

您之后的功能已包含在这些属性中。假设您有SerializableSortedDictionary<string, Car>其中Car是带有子类VolvoAudi的类。

[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序列化程序如何保留有关类型的信息。基本上不可能猜测&#34;从序列化对象中键入而不指定它。它不适用于通用XmlInclude未指定的泛型类型 - 这是一个安全功能(如果攻击者可以让你在他和#39时制作他喜欢的任何对象的实例;重新解析xml feed可能会遇到严重问题。)

答案 1 :(得分:2)

您可以使用自定义XmlReader (或在某些static / thread-local-storage变量中传递类型)。 IdeOne example

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
Key = foo, Name = foo, One = FOO, Type = 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变量(或线程本地存储用于多个线程,或 静态同步(弱)字典 )用于其他配置。 (不完美,似乎你可以使用选项2.)