How to serialize 'System.Numerics.Complex' using an XmlSerializer? - c#

This is the XML output I get when a Complex[] object is serialized:
<MyClass>
<Complex />
<Complex />
<Complex />
<Complex />
</MyClass>
The Complex struct is marked as serializable, and being a struct, it has an implicit parameterless constructor. So why isn't each Complex object serializing its real and imaginary parts ? Does it have to do with the fact that the 'Real' and 'Imaginary' properties of the struct have getters but not setters ?
Thanks.

It depends on the implementation of the serializer you are using to serialize the object.
If you try this, you will get what you are expecting:
using System.IO;
using System.Numerics;
using System.Runtime.Serialization.Formatters.Soap;
public class Test {
public static void Main() {
var c = new Complex(1, 2);
Stream stream = File.Open("data.xml", FileMode.Create);
SoapFormatter formatter = new SoapFormatter();
formatter.Serialize(stream, c);
stream.Close();
}
}
Instead, if you use classes in the System.Xml.Serialization namespace, you will get something similar to what you posted:
using System;
using System.IO;
using System.Numerics;
using System.Xml.Serialization;
public class Test {
public static void Main() {
var c = new Complex(1, 2);
XmlSerializer s = new XmlSerializer(typeof(Complex));
StringWriter sw = new StringWriter();
s.Serialize(sw, c);
Console.WriteLine(sw.ToString());
}
}
I think that this is due to the fact that the XmlSerializer will not serialize private members (as are m_real and m_imaginary in the Complex struct).

The XmlSerializer doesn't serialize properties which don't have a setter (IIRC it only serializers public properties with both public getter and setters). You have a few options:
Replace the System.Numerics.Complex type with a type which you create (and has a "full" property)
Change the MyClass class to handle the serialization (and deserialization) of the complex numbers, via the IXmlSerializable interface.
The second option is shown below.
public class StackOverflow_10523009
{
public class MyClass : IXmlSerializable
{
public Complex[] ComplexNumbers;
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement("MyClass");
List<Complex> numbers = new List<Complex>();
while (reader.IsStartElement("Complex"))
{
Complex c = new Complex(
double.Parse(reader.GetAttribute("Real")),
double.Parse(reader.GetAttribute("Imaginary")));
numbers.Add(c);
reader.Skip();
}
reader.ReadEndElement();
this.ComplexNumbers = numbers.ToArray();
}
public void WriteXml(XmlWriter writer)
{
foreach (var complex in ComplexNumbers)
{
writer.WriteStartElement("Complex");
writer.WriteStartAttribute("Real"); writer.WriteValue(complex.Real); writer.WriteEndAttribute();
writer.WriteStartAttribute("Imaginary"); writer.WriteValue(complex.Imaginary); writer.WriteEndAttribute();
writer.WriteEndElement();
}
}
public override string ToString()
{
return "MyClass[" + string.Join(",", ComplexNumbers) + "]";
}
}
public static void Test()
{
MyClass mc = new MyClass { ComplexNumbers = new Complex[] { new Complex(3, 4), new Complex(0, 1), new Complex(1, 0) } };
XmlSerializer xs = new XmlSerializer(typeof(MyClass));
MemoryStream ms = new MemoryStream();
xs.Serialize(ms, mc);
Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
ms.Position = 0;
MyClass mc2 = (MyClass)xs.Deserialize(ms);
Console.WriteLine(mc2);
}
}

Related

How to to serialize a class with a private dictionary member into a file?

I am trying to write functions that save a type, the Dispatcher type shown below, into a file, and then reload it later. I wrote that following functions, but they are not working. I get an exception:
Exception thrown: 'System.InvalidOperationException' in System.Xml.dll
Elsewhere I read that maybe because the member in the class is private then I should use BinaryFormatter, but it did not work.
What am I doing wrong ?
(The Dispatcher class will be used to store messages and also will allow users to pull messages from it. so I want to backup the data in case of an error and then be able to reload it).
public class Dispatcher
{
private Dictionary<int, Dictionary<int, Dictionary<int, Message>>> m_DataBase;
private Dispatcher()
{
m_DataBase = new Dictionary<int, Dictionary<int, Dictionary<int, Message>>>();
}
public static Dispatcher LoadFromFile()
{
Dispatcher loadedDataBase;
try
{
using (Stream stream = new FileStream(#".\DispatcherDataBase.xml", FileMode.Open))
{
XmlSerializer serializer = new XmlSerializer(typeof(Dispatcher));
loadedDataBase = serializer.Deserialize(stream) as Dispatcher;
}
}
catch (Exception)
{
loadedDataBase = new Dispatcher();
}
return loadedDataBase;
}
public void SaveToFile()
{
FileMode wantedFileModeForStream;
try
{
if (File.Exists(#".\DispatcherDataBase.xml"))
{
wantedFileModeForStream = FileMode.Truncate;
}
else
{
wantedFileModeForStream = FileMode.CreateNew;
}
using (Stream stream = new FileStream(#".\DispatcherDataBase.xml", wantedFileModeForStream))
{
XmlSerializer serializer = new XmlSerializer(this.GetType());
serializer.Serialize(stream, this);
}
}
catch (Exception)
{
}
}
}
You are trying to use XmlSerializer to serialize your Dispatcher type and are encountering three separate problems:
XmlSerializer requires your type to have a public parameterless constructor.
XmlSerializer does not support dictionaries.
XmlSerializer will not serialize non-public members such as m_DataBase.
DataContractSerializer (as well as DataContractJsonSerializer) do not have these limitations. If you mark your Dispatcher type with data contract attributes you will be able to serialize it to XML using this serializer.
Thus if you modify your type as follows:
public static class Constants
{
public const string DataContractNamespace = ""; // Or whatever
}
[DataContract(Name = "Dispatcher", Namespace = Constants.DataContractNamespace)]
public partial class Dispatcher
{
[DataMember]
private Dictionary<int, Dictionary<int, Dictionary<int, Message>>> m_DataBase;
private Dispatcher()
{
m_DataBase = new Dictionary<int, Dictionary<int, Dictionary<int, Message>>>();
}
[System.Runtime.Serialization.OnDeserialized]
void OnDeserializedMethod(System.Runtime.Serialization.StreamingContext context)
{
// Ensure m_DataBase is not null after deserialization (DataContractSerializer does not call the constructor).
if (m_DataBase == null)
m_DataBase = new Dictionary<int, Dictionary<int, Dictionary<int, Message>>>();
}
internal const string FileName = #"DispatcherDataBase.xml";
public static Dispatcher LoadFromFile()
{
Dispatcher loadedDataBase;
try
{
using (var stream = new FileStream(FileName, FileMode.Open))
{
var serializer = new DataContractSerializer(typeof(Dispatcher));
loadedDataBase = serializer.ReadObject(stream) as Dispatcher;
}
}
catch (Exception)
{
loadedDataBase = new Dispatcher();
}
return loadedDataBase;
}
public void SaveToFile()
{
using (var stream = new FileStream(FileName, FileMode.Create))
using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) // Optional indentation for readability only.
{
var serializer = new DataContractSerializer(this.GetType());
serializer.WriteObject(writer, this);
}
}
}
// Not shown in question, added as an example
[DataContract(Name = "Message", Namespace = Constants.DataContractNamespace)]
public class Message
{
[DataMember]
public string Value { get; set; }
}
You will be able to round-trip your Dispatcher class to XML. Demo fiddle #1 here.
Alternatively, you could use DataContractJsonSerializer and serialize to JSON by simply swapping serializers and eliminating the optional XmlWriter:
public static Dispatcher LoadFromFile()
{
Dispatcher loadedDataBase;
try
{
using (var stream = new FileStream(FileName, FileMode.Open))
{
var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(Dispatcher));
loadedDataBase = serializer.ReadObject(stream) as Dispatcher;
}
}
catch (Exception)
{
loadedDataBase = new Dispatcher();
}
return loadedDataBase;
}
public void SaveToFile()
{
using (var stream = new FileStream(FileName, FileMode.Create))
{
var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(this.GetType());
serializer.WriteObject(stream, this);
}
}
Demo fiddle #2 here, which results in a fairly simple, clean-looking serialization format:
{"m_DataBase":[{"Key":1,"Value":[{"Key":2,"Value":[{"Key":3,"Value":{"Value":"hello"}}]}]}]}
json.net could also be used to serialize this type if you are willing to use a 3rd party component.

Writing compact xml with XmlDictionaryWriter.CreateBinaryWriter and a XmlDictionary

I want to write an xml document to disk in a compact format. To this end, I use the net framework method XmlDictionaryWriter.CreateBinaryWriter(Stream stream,IXmlDictionary dictionary)
This method writes a custom compact binary xml representation, that can later be read by XmlDictionaryWriter.CreateBinaryReader. The method accepts an XmlDictionary that can contain common strings, so that those strings do not have to be printed in the output each time. Instead of the string, the dictionary index will be printed in the file. CreateBinaryReader can later use the same dictionary to reverse the process.
However the dictionary I pass is apparently not used. Consider this code:
using System.IO;
using System.Xml;
using System.Xml.Linq;
class Program
{
public static void Main()
{
XmlDictionary dict = new XmlDictionary();
dict.Add("myLongRoot");
dict.Add("myLongAttribute");
dict.Add("myLongValue");
dict.Add("myLongChild");
dict.Add("myLongText");
XDocument xdoc = new XDocument();
xdoc.Add(new XElement("myLongRoot",
new XAttribute("myLongAttribute", "myLongValue"),
new XElement("myLongChild", "myLongText"),
new XElement("myLongChild", "myLongText"),
new XElement("myLongChild", "myLongText")
));
using (Stream stream = File.Create("binaryXml.txt"))
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dict))
{
xdoc.WriteTo(writer);
}
}
}
The produced output is this (binary control characters not shown)
#
myLongRootmyLongAttribute˜myLongValue#myLongChild™
myLongText#myLongChild™
myLongText#myLongChild™
myLongText
So apparently the XmlDictionary has not been used. All strings appear in their entirety in the output, even multiple times.
This is not a problem limited to XDocument. In the above minimal example I used a XDocument to demonstrate the problem, but originally I stumbled upon this while using XmlDictionaryWriter in conjunction with a DataContractSerializer, as it is commonly used. The results were the same:
[Serializable]
public class myLongChild
{
public double myLongText = 0;
}
...
using (Stream stream = File.Create("binaryXml.txt"))
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dict))
{
var dcs = new DataContractSerializer(typeof(myLongChild));
dcs.WriteObject(writer, new myLongChild());
}
The resulting output did not use my XmlDictionary.
How can I get XmlDictionaryWriter to use the suplied XmlDictionary?
Or have I misunderstood how this works?
with the DataContractSerializer approach, I tried debugging the net framework code (visual studio/options/debugging/enable net. framework source stepping). Apparently the Writer does attempt to lookup each of the above strings in the dictionary, as expected. However the lookup fails in line 356 of XmlbinaryWriter.cs, for reasons that are not clear to me.
Alternatives I have considered:
There is an overload for XmlDictionaryWriter.CreatebinaryWriter, that also accepts a XmlBinaryWriterSession. The writer then adds any new strings it encounters into the session dictionary. However, I want to only use a static dictionary for reading and writing, which is known beforehand.
I could wrap the whole thing into a GzipStream and let the compression take care of the multiple instances of strings. However, this would not compress the first instance of each string, and seems like a clumsy workaround overall.
Yes there is a misunderstanding. XmlDictionaryWriter is primarily used for serialization of objects and it is child class of XmlWriter. XDocument.WriteTo(XmlWriter something) takes XmlWriter as argument. The call XmlDictionaryWriter.CreateBinaryWriter will create an instance of System.Xml.XmlBinaryNodeWriter internally. This class has both methods for "regular" writing:
// override of XmlWriter
public override void WriteStartElement(string prefix, string localName)
{
// plain old "xml" for me please
}
and for dictionary based approach:
// override of XmlDictionaryWriter
public override void WriteStartElement(string prefix, XmlDictionaryString localName)
{
// I will use dictionary to hash element names to get shorter output
}
The later is mostly used if you serialize object via DataContractSerializer (notice its method WriteObject takes argument of both XmlDictionaryWriter and XmlWriter type), while XDocument takes just XmlWriter.
As for your problem - if I were you I'd make my own XmlWriter:
class CustomXmlWriter : XmlWriter
{
private readonly XmlDictionaryWriter _writer;
public CustomXmlWriter(XmlDictionaryWriter writer)
{
_writer = writer;
}
// override XmlWriter methods to use the dictionary-based approach instead
}
UPDATE (based on your comment)
If you indeed use DataContractSerializer you have few mistakes in your code.
1) POC classes have to be decorated with [DataContract] and [DataMember] attribute, the serialized value should be property and not field; also set namespace to empty value or you'll have to deal with namespaces in your dictionary as well. Like:
namespace XmlStuff {
[DataContract(Namespace = "")]
public class myLongChild
{
[DataMember]
public double myLongText { get; set; }
}
[DataContract(Namespace = "")]
public class myLongRoot
{
[DataMember]
public IList<myLongChild> Items { get; set; }
}
}
2) Provide instance of session as well; for null session the dictionary writer uses default (XmlWriter-like) implementation:
// order matters - add new items only at the bottom
static readonly string[] s_Terms = new string[]
{
"myLongRoot", "myLongChild", "myLongText",
"http://www.w3.org/2001/XMLSchema-instance", "Items"
};
public class CustomXmlBinaryWriterSession : XmlBinaryWriterSession
{
private bool m_Lock;
public void Lock() { m_Lock = true; }
public override bool TryAdd(XmlDictionaryString value, out int key)
{
if (m_Lock)
{
key = -1;
return false;
}
return base.TryAdd(value, out key);
}
}
static void InitializeWriter(out XmlDictionary dict, out XmlBinaryWriterSession session)
{
dict = new XmlDictionary();
var result = new CustomXmlBinaryWriterSession();
var key = 0;
foreach(var term in s_Terms)
{
result.TryAdd(dict.Add(term), out key);
}
result.Lock();
session = result;
}
static void InitializeReader(out XmlDictionary dict, out XmlBinaryReaderSession session)
{
dict = new XmlDictionary();
var result = new XmlBinaryReaderSession();
for (var i = 0; i < s_Terms.Length; i++)
{
result.Add(i, s_Terms[i]);
}
session = result;
}
static void Main(string[] args)
{
XmlDictionary dict;
XmlBinaryWriterSession session;
InitializeWriter(out dict, out session);
var root = new myLongRoot { Items = new List<myLongChild>() };
root.Items.Add(new myLongChild { myLongText = 24 });
root.Items.Add(new myLongChild { myLongText = 25 });
root.Items.Add(new myLongChild { myLongText = 27 });
byte[] buffer;
using (var stream = new MemoryStream())
{
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dict, session))
{
var dcs = new DataContractSerializer(typeof(myLongRoot));
dcs.WriteObject(writer, root);
}
buffer = stream.ToArray();
}
XmlBinaryReaderSession readerSession;
InitializeReader(out dict, out readerSession);
using (var stream = new MemoryStream(buffer, false))
{
using (var reader = XmlDictionaryReader.CreateBinaryReader(stream, dict, new XmlDictionaryReaderQuotas(), readerSession))
{
var dcs = new DataContractSerializer(typeof(myLongRoot));
var rootCopy = dcs.ReadObject(reader);
}
}
}

Xml being deserialized into base class instead of derived classes

I know this is a popular topic and I have researched extensively without finding an answer to my problem.
I have a base class IntroductionAction and 2 derived classes IntroductionActionComplex and IntroductionActionSimple. I have a list of IntroductionAction objects to which I have added objects of both of the derived types. My classes are as follows:
[XmlInclude(typeof(IntroductionActionComplex))]
[XmlInclude(typeof(IntroductionActionSimple))]
public class IntroductionAction
{
public IntroductionAction() { }
}
public class IntroductionActionComplex : IntroductionAction
{
[XmlIgnore]
public string name { get; set; }
[XmlElement(ElementName = "QuestionString")]
public string question { get; set; }
[XmlElement(ElementName = "AnswerString")]
public List<string> answerStrings { get; set; }
public IntroductionActionComplex()
{
name = string.Empty;
question = null;
answerStrings = new List<string>();
}
}
public class IntroductionActionSimple : IntroductionAction
{
[XmlIgnore]
public string name { get; set; }
[XmlText]
public string Value { get; set; }
public IntroductionActionSimple()
{
Value = string.Empty;
}
}
I then create the List as follows
[XmlElement("IntroductionAction")]
public List<IntroductionAction> introductionActions { get; set; }
I am using XmlSerializer and everything serializes correctly. This is the resulting XML of the list containing one of each of the derived classes which is correct.
<IntroductionAction>
<QuestionString>
test
</QuestionString>
<AnswerString>
test
</AnswerString>
<AnswerString>
test
</AnswerString>
</IntroductionAction>
<IntroductionAction>
test
</IntroductionAction>
This XML file is going onto a device which doesn't read it as XML but just searches for the tags and does whatever work it needs to do and because of that the file can't contain any XSI or XSD tags, indentation, etc that is usually associated with proper XML.
My deserialization code is straight forward:
public static T Deserialize_xml_Config<T>(string file1, T obj)
{
XmlSerializer deserializer = new XmlSerializer(obj.GetType());
using (TextReader reader = new StreamReader(file1))
{
return (T)deserializer.Deserialize(reader);
}
}
Finally to my problem. When I deserialize, it is being deserialized to the base class IntroductionAction and not to the derived classes.
These IntroductionAction classes are just part of a much larger object that I am serializing/deserializing. I have tried making the base class abstract since it contains no functionality but I get an error on deserialization saying
The specified type is abstract: name='IntroductionAction'
Despite my XmlIncludes it seems unable to find the derived classes.
I have tried adding the types to the serializer but that didn't work.
Any help is much appreciated.
Edit:
This is what I mean by adding the types to the serializer
XmlSerializer deserializer = new XmlSerializer(obj.GetType(), new Type [] { typeof(IntroductionActionComplex), typeof(IntroductionActionSimple) });
using (TextReader reader = new StreamReader(file1))
{
return (T)deserializer.Deserialize(reader);
}
Also my attempt at using XmlAttributeOverrides:
XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();
var attrs = new XmlAttributes();
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "IntroductionAction";
attr.Type = typeof(IntroductionActionComplex);
attrs.XmlElements.Add(attr);
attr.ElementName = "IntroductionAction";
attr.Type = typeof(IntroductionActionSimple);
attrs.XmlElements.Add(attr);
attrOverrides.Add(typeof(IntroductionAction), "IntroductionAction", attrs);
XmlSerializer deserializer = new XmlSerializer(obj.GetType(), attrOverrides);
using (TextReader reader = new StreamReader(file1))
{
return (T)deserializer.Deserialize(reader);
}
I think you are pretty close. Below is the full example of saving and loading the XML file based on derived class types. This will save the nodes as the derived type itself, so loading back in will keep the desired type, rather than convert back to the base type. You'll probably need to add exception handling, this was just a quick solution. I did not change your base IntroductionAction or the derived IntroductionActionComplex / IntroductionActionSimple classes.
public class RootNode
{
[XmlElement("IntroductionAction")]
public List<IntroductionAction> introductionActions { get; set; }
public RootNode()
{
introductionActions = new List<IntroductionAction>();
}
private static XmlAttributeOverrides GetXmlAttributeOverrides()
{
XmlAttributeOverrides xml_attr_overrides = new XmlAttributeOverrides();
XmlAttributes xml_attrs = new XmlAttributes();
xml_attrs.XmlElements.Add(new XmlElementAttribute(typeof(IntroductionActionComplex)));
xml_attrs.XmlElements.Add(new XmlElementAttribute(typeof(IntroductionActionSimple)));
xml_attr_overrides.Add(typeof(RootNode), "introductionActions", xml_attrs);
return xml_attr_overrides;
}
// Add exception handling
public static void SaveToFile(RootNode rootNode, string fileName)
{
using (MemoryStream mem_stream = new MemoryStream())
{
XmlSerializer serializer = new XmlSerializer(rootNode.GetType(), RootNode.GetXmlAttributeOverrides());
serializer.Serialize(mem_stream, rootNode);
using (BinaryWriter output = new BinaryWriter(new FileStream(fileName, FileMode.Create)))
{
output.Write(mem_stream.ToArray());
}
}
}
// Add exception handling
public static RootNode LoadFromFile(string fileName)
{
if (File.Exists(fileName))
{
using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (TextReader reader = new StreamReader(file))
{
XmlSerializer serializer = new XmlSerializer(typeof(RootNode), RootNode.GetXmlAttributeOverrides());
return (RootNode)serializer.Deserialize(reader);
}
}
}
return null;
}
}
Test program:
internal class Program
{
private static void Main(string[] args)
{
RootNode obj = new RootNode();
obj.introductionActions.Add(new IntroductionActionComplex() { question = "qTest", answerStrings = { "aTest1", "aTest2" }, name = "aName1" });
obj.introductionActions.Add(new IntroductionActionSimple() { name = "aName2", Value = "aValue" });
RootNode.SaveToFile(obj, "Test.xml");
RootNode obj2 = RootNode.LoadFromFile("Test.xml");
}
}

Using ProtoBuf-net to deseriailize a derived type (dictionary) does not properly set object fields

I'm trying to serialize and then deserialize an object whose class derives from a Dictionary<string,int> with a string member field.
public class TempClass : Dictionary<string, int>
{
public string Version;
public TempClass() { }
}
I've written a unit test to capture the problem that I'm running into: the member field is not set when either serializing or deserializing (to/from a byte[]) with protobuf-net. This test fails in the final Assert when validating that the deserialized Version is properly set. It is always set to null instead of the proper "someVersion".
[TestClass]
public class serializationTest
{
[TestMethod]
public void TestMethod1()
{
string newVersion = "someVersion";
TempClass original = new TempClass()
{
{"a", 2},
{"b", 3},
{"c", 1},
};
original.Version = newVersion;
byte[] serialized = Serialize(original);
TempClass deserialized = Deserialize(serialized);
// Validate
foreach (var pair in original)
{
Assert.IsTrue(deserialized.ContainsKey(pair.Key));
Assert.AreEqual(pair.Value, deserialized[pair.Key]);
}
Assert.AreEqual(newVersion, original.Version, "original mapping version not set correctly");
Assert.AreEqual(newVersion, deserialized.Version, "deserialized version doesn't match");
}
private static TempClass Deserialize(byte[] serialized)
{
TempClass deserialized;
using (MemoryStream ms = new MemoryStream())
{
ms.Write(serialized, 0, serialized.Length);
ms.Position = 0;
deserialized = Serializer.Deserialize<TempClass>(ms);
}
return deserialized;
}
private static byte[] Serialize(TempClass mapping)
{
byte[] serialized;
using (MemoryStream ms = new MemoryStream())
{
Serializer.Serialize(ms, mapping);
serialized = ms.ToArray();
}
return serialized;
}
}
I've already attempted the same work with the BinaryFormatter and also the DataContractSerializer to no avail. Can someone please help me spot where I've goofed that causes this test to fail?
Followup question: If instead I redefine the TempClass like this, the constructor is always called instead of setting the member field properly to the original Version. How can I deserialize without the constructor creating a new Version and instead just copy the original one?
public class TempClass : Dictionary<string, int>
{
public string Version;
public TempClass()
{
Version = DateTime.UtcNow.ToString("s");
}
}
May be this effect related to internal realization of serialization of IDictionary types in protobuf.
You can add version data in dictionary or rewrite yours dto object like this sample.
If you use this data object, it will fix your test:
[ProtoContract]
public class TempClass
{
[ProtoMember(1)]
public Dictionary<string, int> data;
[ProtoMember(2)]
public string Version;
public TempClass() { }
}
The third way is to write your own serialization.
[ProtoContract]
public class TempClass
{
[ProtoMember(1)]
public Dictionary<string, int> data;
[ProtoMember(2)]
public string Version;
public TempClass() { }
}
[TestClass]
public class serializationTest
{
[TestMethod]
public void TestMethod1()
{
string newVersion = "someVersion";
TempClass original = new TempClass()
{
data = new Dictionary<string,int>
{
{"a", 2},
{"b", 3},
{"c", 1},
},
Version = newVersion
};
byte[] serialized = Serialize(original);
TempClass deserialized = Deserialize(serialized);
// Validate
foreach (var pair in original.data)
{
Assert.IsTrue(deserialized.data.ContainsKey(pair.Key));
Assert.AreEqual(pair.Value, deserialized.data[pair.Key]);
}
Assert.AreEqual(newVersion, original.Version, "original mapping version not set correctly");
Assert.AreEqual(newVersion, deserialized.Version, "deserialized version doesn't match");
}
private static TempClass Deserialize(byte[] serialized)
{
TempClass deserialized;
using (MemoryStream ms = new MemoryStream())
{
ms.Write(serialized, 0, serialized.Length);
ms.Position = 0;
deserialized = Serializer.Deserialize<TempClass>(ms);
}
return deserialized;
}
private static byte[] Serialize(TempClass mapping)
{
byte[] serialized;
using (MemoryStream ms = new MemoryStream())
{
Serializer.Serialize(ms, mapping);
serialized = ms.ToArray();
}
return serialized;
}
}
Use [ProtoContract(UseProtoMembersOnly = true, IgnoreListHandling = true)]
instead of [ProtoContract] solves the problem for me.
This will prevent protobuf-net to serialize the class with dictionary rules. (See this)
Here is an example.
[ProtoContract(UseProtoMembersOnly = true, IgnoreListHandling = true)]
public class ProtobufTest<T, P> : IDictionary<T, P>
{
[ProtoMember(1)]
private readonly Dictionary<T, P> _dataset;
[ProtoMember(2)]
public string Name;
private ProtobufTest(Dictionary<T, P> dataset, string name)
{
_dataset = dataset ?? new Dictionary<T, P>();
Name = name;
}
public ProtobufTest(string name) : this(new Dictionary<T, P>(), name) { }
private ProtobufTest() : this(null, string.Empty) {}
//
// IDictionary implementation is omitted.
//
}
Here is an example unit test.
[Test]
public void ProtobufTestNameSerializeDeserialize()
{
ProtobufTest<double, double> t = new ProtobufTest<double, double>("233");
ProtobufTest<double, double> d;
using (MemoryStream ms = new MemoryStream())
{
Serializer.SerializeWithLengthPrefix(ms, t, PrefixStyle.Base128);
ms.Position = 0;
d = Serializer.DeserializeWithLengthPrefix<ProtobufTest<double, double>>(ms, PrefixStyle.Base128);
}
Assert.AreEqual(t.Name, d.Name);
}
(These codes are examples ONLY)

XmlSerializer serialize generic List of interface

I'm trying to use the XmlSerializer to persist a List(T) where T is an interface. The serializer does not like interfaces. I'm curious if there is a simple way to serialize a list of heterogeneous objects easily with XmlSerializer. Here's what I'm going for:
public interface IAnimal
{
int Age();
}
public class Dog : IAnimal
{
public int Age()
{
return 1;
}
}
public class Cat : IAnimal
{
public int Age()
{
return 1;
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
var animals = new List<IAnimal>
{
new Dog(),
new Cat()
};
var x = new XmlSerializer(animals.GetType());
var b = new StringBuilder();
var w = XmlTextWriter.Create(b, new XmlWriterSettings { NewLineChars = "\r\n", Indent = true });
//FAIL - cannot serialize interface. Does easy way to do this exist?
x.Serialize(w, animals);
var s = b.ToString();
}
You can use XmlSerializer as well, but you need to include all the possible types that can appear in the object graph you're serializing, which limits extensibility and lowers maintainability. You can do it by using an overload of the constructor of XmlSerializer:
var x = new XmlSerializer(animals.GetType(), new Type[] { typeof(Cat), typeof(Dog) });
Also, there are several issues of note when using XmlSerializer, all of the outlined here (MSDN) - for example look under the heading 'Dynamically generated assemblies'.
The XmlSerializer can't handle an interface because it doesn't know which types to create when deserialising. To get around this you need to handle that part of the serialization yourself by implementing the IXmlSerializable interface. This allows you to record the type so you can re-create (deserialise) it.
The ListOfIAnimal class below shows how I inherited and extended the generic list List<IAnimal> to implement the required interface. I squished up your old classes adding an extra non-interface field to each so I could see that the concrete classes were getting serialised and deserialised properly.
Compared to your code I'm just using the new type ListOfIAnimal in place of List<IAnimal>, the other changes are just a little refactoring.
Its complete code, just copy it into it's own .cs file, call the first function to step through it.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace Serialiser
{
static class SerialiseInterface
{
public static void SerialiseAnimals()
{
String finalXml;
// Serialize
{
var animals = new ListOfIAnimal{
new Dog() { Age = 5, Teeth = 30 },
new Cat() { Age = 6, Paws = 4 }
};
var xmlSerializer = new XmlSerializer(animals.GetType());
var stringBuilder = new StringBuilder();
var xmlTextWriter = XmlTextWriter.Create(stringBuilder, new XmlWriterSettings { NewLineChars = "\r\n", Indent = true });
xmlSerializer.Serialize(xmlTextWriter, animals);
finalXml = stringBuilder.ToString();
}
// Deserialise
{
var xmlSerializer = new XmlSerializer(typeof(ListOfIAnimal));
var xmlReader = XmlReader.Create(new StringReader(finalXml));
ListOfIAnimal animals = (ListOfIAnimal)xmlSerializer.Deserialize(xmlReader);
}
}
}
public class ListOfIAnimal : List<IAnimal>, IXmlSerializable
{
public ListOfIAnimal() : base() { }
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement("ListOfIAnimal");
while (reader.IsStartElement("IAnimal"))
{
Type type = Type.GetType(reader.GetAttribute("AssemblyQualifiedName"));
XmlSerializer serial = new XmlSerializer(type);
reader.ReadStartElement("IAnimal");
this.Add((IAnimal)serial.Deserialize(reader));
reader.ReadEndElement(); //IAnimal
}
reader.ReadEndElement(); //ListOfIAnimal
}
public void WriteXml(XmlWriter writer)
{
foreach (IAnimal animal in this)
{
writer.WriteStartElement("IAnimal");
writer.WriteAttributeString("AssemblyQualifiedName", animal.GetType().AssemblyQualifiedName);
XmlSerializer xmlSerializer = new XmlSerializer(animal.GetType());
xmlSerializer.Serialize(writer, animal);
writer.WriteEndElement();
}
}
#endregion
}
public interface IAnimal { int Age { get; set; } }
public class Dog : IAnimal { public int Age { get; set;} public int Teeth { get; set;} }
public class Cat : IAnimal { public int Age { get; set;} public int Paws { get; set;} }
}
I thought about leaving deserialize as an exercise for the reader, but the code would'n be very useful without it.
Do you have to use XmlSerializer? This is a known issue with XmlSerializer.
You can use BinaryFormatter to save to a stream:
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, animals);
Other alternative is to use WCF's DataContractSerializer and provide types using KnownType attribute.
You can use ExtendedXmlSerializer.
var serializer = new ExtendedXmlSerializer();
var xml = serializer.Serialize(animals);
Your xml will look like:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfIAnimal>
<Dog type="Model.Dog" />
<Cat type="Model.Cat" />
</ArrayOfIAnimal>
The easy way is to add the [Serializable()] decoration to your classes
and change your IList to List and see if that works.
If you use interfaces then go see webturner's answer.

Categories