I use C# 3 on microsoft .net 3.5 (VS2008). I have a problem with de-serialization. I use DataContract and DataMember in a hierarchy of classes that I want to be serializable.
However, I also have polymorphism in one container, so I need to pass a list of known types to the serializers. My collection is a serializable dictionary that I found on the net:
[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
}
This is the one that holds a polymorphic object tree in its TVal. So you see I have modified the original code to add a list of known types, which works well for serialization, because I set this list in my superior classes constructors. (the classes that holds the dictionary instance).
This list of known types happens to be discovered at runtime, using this function:
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;
}
}
This dynamic system allows me to discover the known types by just knowing the base class. Which is something I always wondered why do the framework does not provide this feature... but well..
So my issue is that, my serializable dictionary, is an utility class, I can not specialize it to hardcode the list of known types, even less so because it is discovered at run time. Deserialization works on uninitialized object, and therefore I can not provide the list of known types to the dictionary de-serializer.
Of course, for the moment, I will workaround that problem by discovering the list of known types using my FindAllDerivedTypes
functions on TVal directly in the dictionary.
But as it is less scalable than an exeternally-provided type list, I'd like to know if anyone can provide me with a real fix.
thanks a lot.
You can use custom XmlReader (or pass the types in some static / thread-local-storage variable). 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
}
}
Output:
Key = bar, Name = bar, Two = BAR, Type = ElementTwo Key = foo, Name = foo, One = FOO, Type = ElementOne
You can comment out the passing of the types and deserialization fails: using(XmlReader rd = new KnownTypesXmlReader(mem) /* { ExtraTypes = extraTypes } */)
ADDED COMMENTS:
I was searching for the solution in this order:
[XmlInclude]
) if you can. (Your runtime constraint disallows this.)void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
thus XmlReader
was perfect candidate. I'd suggest using iterface
for final solution (e.g. interface KnownTypes { Type[] GetKnownTypes(object me, string hint, params Type[] involved); }
)static
variable (or thread-local-storage for use with multiple threads, or static synchronized(weak)dictionary) for additional configuration. (Not perfect and it seems that you can use option 2.)