Well I asked before how to write an Dictionary to an File and was referenced to http://web.archive.org/web/20100703052446/http://blogs.msdn.com/b/psheill/archive/2005/04/09/406823.aspx
So, I am trying to use it but I have the Problem that it is always generating an empty File.
What am I doing wrong?
I have:
private static void Serialize(TextWriter writer, IDictionary dictionary)
{
List<Entry> entries = new List<Entry>(dictionary.Count);
foreach (object key in dictionary.Keys)
{
entries.Add(new Entry(key, dictionary[key]));
}
System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer(typeof(List<Entry>));
serializer.Serialize(writer, entries);
}
private static void Deserialize(TextReader reader, IDictionary dictionary)
{
dictionary.Clear();
XmlSerializer serializer = new XmlSerializer(typeof(List<Entry>));
List<Entry> list = (List<Entry>)serializer.Deserialize(reader);
foreach (Entry entry in list)
{
dictionary[entry.Key] = entry.Value;
}
}
public class Entry
{
public object Key;
public object Value;
public Entry()
{
}
public Entry(object key, object value)
{
Key = key;
Value = value;
}
}
}
private void saveConfig()
{
TextWriter writer = File.CreateText("C:\\Users\\test.xml");
Serialize(writer, this.configuration);
}
private Dictionary<String, MyConfig> configuration;
where:
public class MyConfig{ public Item[] items=new Item[64];}
and Item.. is an pretty complex Object.
What am I doing wrong here? How get it to work? Or is it just not possible to pusth that Array in MyConfig to the FIle like that?
EDIT:
public class Item {
public Item(many params){}
uint whatever;
short whatever2;
byte bla;
String name,
List<Wrapper> wrappers;
ItemCache cache;
//many getters
}
public class ItemCache{
public ItemCache(many Params){}
List<CodeCache> cC;
}
public class Wrapper{
List<Cram> crams;
String name;
uint id;
}
The article you link to has a mistake. XmlSerializer requires that all types to be serialized are discoverable statically, in advance. The Entry class however has non-generic object keys and values, so this is not the case.
Instead, use generics, as doing so will statically specify they key and value types:
private static void Serialize<TKey, TValue>(TextWriter writer, IDictionary<TKey, TValue> dictionary)
{
var entries = dictionary.Select(pair => new Entry<TKey, TValue>(pair.Key, pair.Value)).ToList();
var serializer = new XmlSerializer(entries.GetType());
serializer.Serialize(writer, entries);
}
private static void Deserialize<TKey, TValue>(TextReader reader, IDictionary<TKey, TValue> dictionary)
{
var serializer = new XmlSerializer(typeof(List<Entry<TKey, TValue>>));
var list = (List<Entry<TKey, TValue>>)serializer.Deserialize(reader);
dictionary.Clear();
foreach (var entry in list)
{
dictionary[entry.Key] = entry.Value;
}
}
public class Entry<TKey, TValue>
{
public TKey Key;
public TValue Value;
public Entry() { }
public Entry(TKey key, TValue value)
{
Key = key;
Value = value;
}
}
Related
I have some already defined extension method like this:
public static object Get(this IDictionary<string, object> dict, string key)
{
if (dict.TryGetValue(key, out object value))
{
return value;
}
return null;
}
but if I try to use it with an instance of an
IDictionary <string, myClass>
it won't show up. I thought every class derived from object. Questions:
1) Why is this happening?
2) How could I make an extension method that includes all kinds of IDictionary?
This is perfectly working:
using System.Collections.Generic;
namespace ConsoleApp1
{
public class Program
{
public static void Main(string[] args)
{
var dic = new Dictionary<string, object> {{"Test", 1}};
var result = dic.Get("Test");
}
}
public static class MyExtensions
{
public static object Get(this IDictionary<string, object> dict, string key)
{
if (dict.TryGetValue(key, out object value))
{
return value;
}
return null;
}
public static T Get<T>(this IDictionary<string, T> dict, string key)
{
if (dict.TryGetValue(key, out T value))
{
return value;
}
return default(T);
}
}
}
Could someone please explain to me why the following cast does not work and the solution to the problem.
I have a GroupedResult:
public class GroupedResult<TKey, TElement>
{
public TKey Key { get; set; }
private readonly IEnumerable<TElement> source;
public GroupedResult(TKey key, IEnumerable<TElement> source)
{
this.source = source;
this.Key = key;
}
}
public class Bacon
{
}
I would like to cast the List<string, Bacon> to List<string, object>. I have tried the following and other ways.
var list = new List<GroupedResult<string, Bacon>>
{
new GroupedResult<string, Bacon>("1", new List<Bacon>()),
new GroupedResult<string, Bacon>("2", new List<Bacon>())
};
var result = list.Cast<GroupedResult<string, object>>().ToList();
But I always get the following error:
InvalidCastException: Unable to cast object of type 'GroupedResult2[System.String,UserQuery+Bacon]' to type 'GroupedResult2[System.String,System.Object]'.
For that to work you'd have to use an interface instead of class type.
public interface IGroupResult<TKey, out TElement>
{
TKey Key { get; set; }
}
public class GroupedResult<TKey, TElement> : IGroupResult<TKey, TElement>
{
public TKey Key { get; set; }
private readonly IEnumerable<TElement> source;
public GroupedResult(TKey key, IEnumerable<TElement> source)
{
this.source = source;
this.Key = key;
}
}
public class Bacon
{
}
Then you could do something like
IGroupResult<string, Bacon> g = new GroupedResult<string, Bacon>("1", new List<Bacon>());
var result = (IGroupResult<string, object>)g;
That's because co-variance is only allowed on interfaces and delegates, but not classes. Note that you should only mark a type as co-variant if it only comes out of the interface (method return type and read only properties).
Though you should ask yourself why you want to cast something to object when you're working with generics. The main point of generics is to avoid having to use the object type as a catch all and this could indicate a flaw in your design that you might want to rethink.
It would be better to start with a GroupedResult < string, object> then you COULD do this
new GroupedResult<string, object>("2", new List<Bacon>())
why aren'y you using GroupedResult<string, object> instead of GroupedResult<string, Bacon>? like this:
var list = new List<GroupedResult<string, object>>
{
new GroupedResult<string, object>("1", new List<Bacon>()),
new GroupedResult<string, object>("2", new List<Bacon>())
};
You can have a Cast method in your GroupedResult class and use it to do the casting!
public class GroupedResult<TKey, TElement>
{
public TKey Key { get; set; }
private readonly IEnumerable<TElement> source;
public GroupedResult(TKey key, IEnumerable<TElement> source)
{
this.source = source;
this.Key = key;
}
public GroupedResult<TKey, object> Cast()
{
return new GroupedResult<TKey, object>(Key, source.Cast<object>());
}
}
public class Bacon
{
}
static void Main(string[] args)
{
var list = new List<GroupedResult<string, Bacon>>
{
new GroupedResult<string, Bacon>("1", new List<Bacon>()),
new GroupedResult<string, Bacon>("2", new List<Bacon>())
};
// var result = list.Cast<GroupedResult<string, object>>().ToList();
List<GroupedResult<string,object>> result = list.Select(B => B.Cast()).ToList();
}
I'm trying to use a dictionary as a class member. I want to
use a property to get/set the key/value of the dictionary but I'm
confused as how to use a dictionary as a property. Since there are 2
parts, I don't know how to setup the get/sets.
You could try this:
class Example {
private Dictionary<int,string> _map;
public Dictionary<int,string> Map { get { return _map; } }
public Example() { _map = new Dictionary<int,string>(); }
}
Implementation would go along the lines of:
var e = new Example();
e.Map[42] = "The Answer";
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var cl = new cl();
populate(cl.dict);
foreach(var d in cl.dict)
Console.WriteLine(d.Key);
}
private static void populate(Dictionary<int, string> d)
{
for (int i = 0; i < 10 ; i++)
{
if (!d.ContainsKey(i))
{
d.Add(i, i.ToString());
}
}
}
}
public class cl
{
public Dictionary<int, string> dict;
public cl()
{
dict = new Dictionary<int, string>();
}
}
Do you mean this ?
class MyDictionary<TKey, TValue>
{
private readonly Dictionary<TKey, TValue> _dictionary;
public void Add(TKey key, TValue value)
{
_dictionary.Add(key, value);
}
public void Clear()
{
_dictionary.Clear();
}
public bool Remve(TKey key)
{
return _dictionary.Remove(key);
}
.... and other methods...
public MyDictionary(Dictionary<TKey, TValue> dictionary)
{
_dictionary = dictionary;
}
}
I have a class 'products' that is serializable to XML. I'm using the standard System.Xml.Serialization.XmlSerializer to serialize and a XmlWriter 'writer' object to write the serialized results to a StreamWriter object. The serializer object now serializes the whole class in one go:
XmlSerializer serializer = new XmlSerializer(typeof(products));
serializer.Serialize(writer, products);
The class has a Dictionary<string,string> member called 'Specifications'. It is dynamically built, so I don't know the keys beforehand. Here's an example of what data the dictionary may contain (key: value):
color: blue
length: 110mm
width: 55mm
I would like to be able to serialize that property into this:
...
<specifications>
<color>blue</color>
<length>110mm</length>
<width>55mm</width>
</specifications>
...
I know this is poor XML design, but it has to conform to a 3rd party specification.
Is there perhaps a standard attribute that I can use? If not, how would I be able to serialize the dictionary like that?
If you need more code snippets, let me know.
EDIT:
Due to some changes in requirement, I let go of the Dictionary<string,string>. Instead, I created a class "Specification":
public class Specification
{
public string Name;
public string Value;
public bool IsOther;
public Specification() : this(null, null, false) { }
public Specification(string name, string value) : this(name, value, false) { }
public Specification(string name, string value, bool isOther)
{
Name = name;
Value = value;
IsOther = isOther;
}
}
To avoid repeating the element "spec" by having a List of "Specification" in the product class, I use a plural class "Specifications" that implements the IXmlSerializable interface:
public class Specifications: IXmlSerializable
{
public List<Specification> Specs = new List<Specification>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
//I don't need deserialization, but it would be simple enough now.
throw new System.NotImplementedException();
}
public void WriteXml(XmlWriter writer)
{
//write all "standarad", named specs
//this writes the <color>blue</color>-like elements
Specs.Where(s => !s.IsOther).ToList().ForEach(s => writer.WriteElementString(s.Name, s.Value));
//write other specs
//this writes <other_specs>{name|value[;]}*</other_specs>
string otherSpecs = string.Join(";", Specs.Where(s => s.IsOther).Select(s => string.Concat(s.Name, "|", s.Value)));
if (otherSpecs.Length > 0) writer.WriteElementString("other_specs", otherSpecs);
}
}
The class "Specifications" is applied as:
public class Product
{
public Product()
{
Specifications = new Specifications();
}
[XmlElement("specs")]
public Specifications Specifications;
//this "feature" will not include <specs/> when there are none
[XmlIgnore]
public bool SpecificationsSpecified { get { return Specifications.Specs.Any(); } }
//...
}
Thank you for providing examples of IXmlSerializable and XmlWriter. I didn't know that interface and usage of XmlWriter - it proved to be a valuable inspiration for me!
*this was my first SO question. What's the most appropriate way to close it? I didn't provide this as my own answer as it is not a real answer to my initial question (about Dictionary).
Assuming that your dictionary value are all simple types that can be converted to a string, you can create your own IXmlSerializable dictionary wrapper to store and retrieve the keys and values:
public class XmlKeyTextValueListWrapper<TValue> : CollectionWrapper<KeyValuePair<string, TValue>>, IXmlSerializable
{
public XmlKeyTextValueListWrapper() : base(new List<KeyValuePair<string, TValue>>()) { } // For deserialization.
public XmlKeyTextValueListWrapper(ICollection<KeyValuePair<string, TValue>> baseCollection) : base(baseCollection) { }
public XmlKeyTextValueListWrapper(Func<ICollection<KeyValuePair<string, TValue>>> getCollection) : base(getCollection) {}
#region IXmlSerializable Members
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.ReadXml(reader, this, converter);
}
public void WriteXml(XmlWriter writer)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.WriteXml(writer, this, converter);
}
#endregion
}
public static class XmlKeyValueListHelper
{
public static void WriteXml<T>(XmlWriter writer, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
{
foreach (var pair in collection)
{
writer.WriteStartElement(XmlConvert.EncodeName(pair.Key));
writer.WriteValue(typeConverter.ConvertToInvariantString(pair.Value));
writer.WriteEndElement();
}
}
public static void ReadXml<T>(XmlReader reader, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType == XmlNodeType.Element)
{
var key = XmlConvert.DecodeName(reader.Name);
string value;
if (reader.IsEmptyElement)
{
value = string.Empty;
// Move past the end of item element
reader.Read();
}
else
{
// Read content and move past the end of item element
value = reader.ReadElementContentAsString();
}
collection.Add(new KeyValuePair<string,T>(key, (T)typeConverter.ConvertFromInvariantString(value)));
}
// Move past the end of the list element
reader.ReadEndElement();
}
public static void CopyTo<TValue>(this XmlKeyTextValueListWrapper<TValue> collection, ICollection<KeyValuePair<string, TValue>> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
if (collection == null)
dictionary.Clear();
else
{
if (collection.IsWrapperFor(dictionary)) // For efficiency
return;
var pairs = collection.ToList();
dictionary.Clear();
foreach (var item in pairs)
dictionary.Add(item);
}
}
}
public class CollectionWrapper<T> : ICollection<T>
{
readonly Func<ICollection<T>> getCollection;
public CollectionWrapper(ICollection<T> baseCollection)
{
if (baseCollection == null)
throw new ArgumentNullException();
this.getCollection = () => baseCollection;
}
public CollectionWrapper(Func<ICollection<T>> getCollection)
{
if (getCollection == null)
throw new ArgumentNullException();
this.getCollection = getCollection;
}
public bool IsWrapperFor(ICollection<T> other)
{
if (other == Collection)
return true;
var otherWrapper = other as CollectionWrapper<T>;
return otherWrapper != null && otherWrapper.IsWrapperFor(Collection);
}
ICollection<T> Collection { get { return getCollection(); } }
#region ICollection<T> Members
public void Add(T item)
{
Collection.Add(item);
}
public void Clear()
{
Collection.Clear();
}
public bool Contains(T item)
{
return Collection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
Collection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Collection.Count; }
}
public bool IsReadOnly
{
get { return Collection.IsReadOnly; }
}
public bool Remove(T item)
{
return Collection.Remove(item);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return Collection.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
And then use it like so:
[XmlRoot("products")]
public class Products
{
public Products()
{
Specifications = new Dictionary<string, string>();
}
[XmlIgnore]
[JsonProperty("specifications")] // For testing purposes, I compare Json.NET serialization before and after XML serialization. You can remove this.
public Dictionary<string, string> Specifications { get; set; }
[XmlElement("specifications")]
[JsonIgnore] // For testing purposes, I compare Json.NET serialization before and after XML serialization. You can remove this.
public XmlKeyTextValueListWrapper<string> XmlSpecifications
{
get
{
return new XmlKeyTextValueListWrapper<string>(() => this.Specifications);
}
set
{
value.CopyTo(Specifications = (Specifications ?? new Dictionary<string, string>()));
}
}
}
The fact that your dictionary values are simple types (directly convertible from and to text) makes it possible to avoid nested creations of XmlSerializer, which is more complex. See here for an example.
Make the dictionary NonSerialized
[XmlRoot("specifications")]
public class Specifications
{
[NonSerialized]
Dictionary<string, string> dict { get; set; }
[XmlElement("color")]
string color {get;set;}
[XmlElement("length")]
string length { get; set; }
[XmlElement("width")]
string width { get; set; }
public Specifications()
{
dict = new Dictionary<string, string>();
}
}
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:
Use what is already there ([XmlInclude]) if you can. (Your runtime constraint disallows this.)
Customize some class involved - the problem was in 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); })
If above fails (if you were not in control of the deserializer) use 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.)
First of all XmlSerializer ignores [Serializable], [NonSerialized], [DataContract] and [DataMember] attributes - it is controlled either by IXmlSerializable interface (which completely changes behavior of object serialization) or by default it serializes all public members/properties of an object, and you can give hints to XmlSerializer via attributes like [XmlRoot], [XmlAttribute], [XmlIgnore] [XmlArray], [XmlElement], [XmlArrayItem], [XmlInclude], [XmlText], etc.
The functionality you're after is already included in those attributes. Lets assume you have SerializableSortedDictionary<string, Car> where Car is class with subclasses Volvo and Audi.
[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; }
}
}
All you need is to decorate the base class with all possible sub classes via the 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);
}
Deserialization works as well. You may notice xsi:type attribute in resulting xml - that how xml serializer persist information about types. It essentially impossible to "guess" type from serialized object without specifying it. It won't work for generic types, that weren't specified via XmlInclude - that's a security feature (if an attacker can make you make instance of any object he like when you're parsing xml feed you may run into serious troubles).