how to serialize a point3D using XmlSerializer? - c#

public class Placement
{
public Point3D Location { get; set; }
public Point3D Axis { get; set; }
public Point3D Direction { get; set; }
}
public class Attribute1
{
public string Key { get; set; }
public Type Type { get; set; }
public Object Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
Attribute1 a = new Attribute1();
a.Key = "test";
var p = new Placement();
p.Axis = new Point3D(12.0, 22.09, 0);
p.Location = new Point3D(12.0, 22.09, 0);
p.Direction = new Point3D(12.0, 22.09, 0);
a.Value = p;
var serializer = new XmlSerializer(typeof(Attribute1));
var path = "E:\\details.xml";
using (TextWriter writer = new StreamWriter(path))
{
serializer.Serialize(writer, a);
}
Console.Read();
}
}
I am trying to serialize Point3D using XmlSerializer.
I am using XmlSerializer for serializing other properties of the class which contain Attribute1
But I get error while serializing.
Please let me know how to achieve this or point me to relevant resources. Thanks!

You have told it that you want to serialize Attribute1 but not Placement. Just change this one line:
var serializer = new XmlSerializer(typeof(Attribute1), new Type[] { typeof(Placement) });

Related

Instantiate all class objects serialized from xml

I have deserialized a XML file into a class that make my soap request object.
When I'm serializing C# class most of classes object not fill the output xml file.
Example
GetUserReq.Envelope getUser = new GetUserReq.Envelope();
getUserResponse = new GetUserRes.Envelope();
getUser.Body = new GetUserReq.Body();
getUser.Body.GetUser = new GetUserReq.GetUser();
getUser.Body.GetUser.ReturnedTags = new GetUserReq.ReturnedTags();
if (allReturnTags)
{
getUser.Body.GetUser.ReturnedTags.AssociatedGroups = new GetUserReq.AssociatedGroups();
getUser.Body.GetUser.ReturnedTags.AssociatedDevices = new GetUserReq.AssociatedDevices();
getUser.Body.GetUser.ReturnedTags.AssociatedGroups.UserGroup = new GetUserReq.UserGroup() { Name = "", UserRoles = new GetUserReq.UserRoles() };
getUser.Body.GetUser.ReturnedTags.AssociatedGroups.UserGroup.UserRoles = new GetUserReq.UserRoles() { UserRole = "" };
}
For each item nested in the "envelope", I need to create new object otherwise the output xml file will be empty by that tag.
There are any method could do a iteration and made what I need?
These is a snippet code where start Envelope
public class GetUserReq {
[XmlRoot(ElementName = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public class Envelope
{
[XmlElement(ElementName = "Header", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public string Header { get; set; }
[XmlElement(ElementName = "Body", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public Body Body { get; set; }
[XmlAttribute(AttributeName = "soapenv", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Soapenv { get; set; }
[XmlAttribute(AttributeName = "ns", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Ns { get; set; }
}
and go on with body that contains other classes
[XmlRoot(ElementName = "Body", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public class Body
{
[XmlElement(ElementName = "getUser", Namespace = "http://www.cisco.com/AXL/API/9.1")]
public GetUser GetUser { get; set; }
}
You can use reflection.
public object CascadeInitializer(Type type)
{
var newObj = Activator.CreateInstance(type); // create new instance of your target class
Func<PropertyInfo,bool> query = q
=> q.PropertyType.IsClass && // Check if property is a class
q.CanWrite && // Check if property is not readOnly
q.PropertyType != typeof(string); // Check if property is not string
foreach (var el in type.GetProperties().Where(query))
{
// create new instance of el cascade
var elInstance = CascadeInitializer(el.PropertyType);
el.SetValue(newObj, elInstance);
}
return newObj;
}
// a generic overload to easier usage
public T CascadeInitializer<T>() => (T)CascadeInitializer(typeof(T));
usage
var x = CascadeInitializer<Envelope>();
also if you want to control what classes should be automatically initialized, you can add an empty interface interface IInitializable to your classes, this way you can check what property is of IInitializable type in the Func query.
e.g
Func<PropertyInfo,bool> query = q
=> q.PropertyType.IsClass && // Check if property is a class
q.CanWrite && // Check if property is not readOnly
q.PropertyType != typeof(string) && // Check if property is not string
q.PropertyType.GetInterfaces() // Check what classes should be initialize
.Any(i => i.Name == nameof(IInitializable) );
...
public interface IInitializable{}
public class Envelope : IInitializable {
.....
test on dotnetfiddle :
https://dotnetfiddle.net/Xm8nEX
You were only defining classes and did not have the properties for the classes. See code below :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
GetUserReq userReq = new GetUserReq();
userReq.Envelope = new Envelope();
GetUserResponse userResponse = new GetUserResponse();
userResponse.Envelope = new Envelope();
userReq.Body = new Body();
userReq.Body.GetUser = new GetUser();
userReq.Body.GetUser.ReturnedTags = new ReturnedTags();
Boolean allReturnTags = true;
if (allReturnTags)
{
userReq.Body.GetUser.ReturnedTags.AssociatedGroups = new AssociatedGroups();
userReq.Body.GetUser.ReturnedTags.AssociatedDevices = new AssociatedDevices();
userReq.Body.GetUser.ReturnedTags.AssociatedGroups.UserGroup = new UserGroup() { Name = "", UserRoles = new UserRoles() };
userReq.Body.GetUser.ReturnedTags.AssociatedGroups.UserGroup.UserRoles = new UserRoles() { UserRole = "" };
}
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(FILENAME, settings);
XmlSerializer serializer = new XmlSerializer(typeof(GetUserReq));
serializer.Serialize(writer, userReq);
}
}
[XmlRoot(ElementName = "Body", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public class Body
{
[XmlElement(ElementName = "getUser", Namespace = "http://www.cisco.com/AXL/API/9.1")]
public GetUser GetUser { get; set; }
}
public class GetUser
{
public ReturnedTags ReturnedTags { get; set; }
}
public class ReturnedTags
{
public AssociatedGroups AssociatedGroups { get; set; }
public AssociatedDevices AssociatedDevices { get; set; }
}
public class GetUserReq
{
public Envelope Envelope { get; set; }
public Body Body { get; set; }
}
public class GetUserResponse
{
public Envelope Envelope { get; set; }
}
[XmlRoot(ElementName = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public class Envelope
{
[XmlElement(ElementName = "Header", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public string Header { get; set; }
[XmlElement(ElementName = "Body", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public Body Body { get; set; }
[XmlAttribute(AttributeName = "soapenv", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Soapenv { get; set; }
[XmlAttribute(AttributeName = "ns", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Ns { get; set; }
}
public class AssociatedGroups
{
public UserGroup UserGroup { get; set; }
}
public class AssociatedDevices
{
}
public class UserGroup
{
public UserRoles UserRoles { get; set; }
public string Name { get; set; }
}
public class UserRoles
{
public string UserRole { get; set; }
}
}
I did this code made from your example and modified for my neededs
public T AllReturnTags<T>() => (T)AllReturnTags(typeof(T));
public object AllReturnTags(Type type)
{
var newObj = Activator.CreateInstance(type); // create new instance of your target class
Func<PropertyInfo, bool> query = q
=> q.PropertyType.IsClass &&
q.CanWrite;
foreach (var el in type.GetProperties().Where(query))
{
// create new instance of el cascade
if (el.PropertyType == typeof(string))
{
el.SetValue(newObj, "", null);
}
if (el.PropertyType == typeof(Int32))
{
el.SetValue(newObj, 0, null);
}
if (el.PropertyType.IsClass && el.PropertyType != typeof(string) && el.PropertyType != typeof(Int32) && el.PropertyType.IsGenericType == true && el.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
{
var elInstance = AllReturnTags(el.PropertyType);
Type itemType = typeof(List<>).MakeGenericType(elInstance.GetType());
IList res = (IList)Activator.CreateInstance(itemType);
res.Add(elInstance);
try { el.SetValue(newObj, res, null); } catch { };
}
if (el.PropertyType.IsClass && el.PropertyType != typeof(string) && el.PropertyType != typeof(Int32) && el.PropertyType.IsGenericType != true )
{
var elInstance = AllReturnTags(el.PropertyType);
try { el.SetValue(newObj, elInstance, null); } catch { return elInstance; };
}
}
return newObj;
}
This seems to work with single items and lists.
Thx you #AliReza

How to create constructor to fill xmlelement and attribute?

I have complex class which I want to serialize to XML format using costum attributes. I am stucked at XMLelement which is List and I would like to generate new items with constructor, where I can fill attribute name and text value.
Now I have to create seperate objects and those I can add to the List. I want to simplify this.
class to be serialized:
[XmlElement("Cfg")]
public ElCfg Cfg = new ElCfg();
public class ElCfg
{
[XmlAttribute("Name")] public string CfgName { get; set; } = "Default";
[XmlElement("Content")] public ElCont Content = new ElCont();
}
public class ElCont
{
[XmlAttribute("ver")] public string ContentVer { get; set; }
[XmlElement("Prop")] public List<ElProp> Properties = new List<ElProp>();
}
public class ElProp
{
[XmlAttribute("Name")]
public string PropertyName { get; set; }
[XmlText]
public string PropertyVal { get; set; }
}
usage in main:
static void Main(string[] args)
{
//build promotic object
PromoticXML xmlDoc = new PromoticXML();
xmlDoc.Cfg.Content.ContentVer = "80323";
PromoticXML.ElProp prop1 = new PromoticXML.ElProp();
prop1.PropertyName = "neco";
prop1.PropertyVal = "necojineho";
PromoticXML.ElProp prop2 = new PromoticXML.ElProp();
prop2.PropertyName = "neco";
prop2.PropertyVal = "necojineho";
xmlDoc.Cfg.Content.Properties.Add(prop1);
xmlDoc.Cfg.Content.Properties.Add(prop2);
//serialize promotic object
XmlWriterSettings xmlSet = new XmlWriterSettings();
xmlSet.Encoding = Encoding.Unicode;
xmlSet.Indent = true;
xmlSet.IndentChars = " ";
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
XmlSerializer serializer = new XmlSerializer(typeof(PromoticXML));
using (XmlWriter writer = XmlWriter.Create("promotic.xml", xmlSet))
{
serializer.Serialize(writer, xmlDoc);
}
Process.Start("notepad.exe", "promotic.xml");
}
What my goal is:
xmlDoc.Cfg.Content.Properties.Add(new PromoticXML.ElProp("someName", "someText"));
instead of:
PromoticXML.ElProp prop1 = new PromoticXML.ElProp();
prop1.PropertyName = "neco";
prop1.PropertyVal = "necojineho";
xmlDoc.Cfg.Content.Properties.Add(prop1);

How to use protobuf-net for xml-serialization?

Maybe i misunderstood something for serialization. i wanna serialize my .net object fastest way. i made some googling i found protobuf. Myfirstquestion is ProtoBuf.Net has avility for xml serailization.if it has, can i use it for xml serialization.
My model:
[XmlType]
public class CT {
[XmlElement(Order = 1)]
public int Foo { get; set; }
}
[XmlType]
public class TE {
[XmlElement(Order = 1)]
public int Bar { get; set; }
}
[XmlType]
public class TD {
[XmlElement(Order = 1)]
public List CTs { get; set; }
[XmlElement(Order = 2)]
public List TEs { get; set; }
[XmlElement(Order = 3)]
public string Code { get; set; }
[XmlElement(Order = 4)]
public string Message { get; set; }
[XmlElement(Order = 5)]
public DateTime StartDate { get; set; }
[XmlElement(Order = 6)]
public DateTime EndDate { get; set; }
}
my serializer :
CT ct = new CT() { Foo = 1 };
List<CT> list = new List<CT>();
list.Add(ct);
TE te = new TE() { Bar = 2 };
List<TE> list2 = new List<TE>();
list2.Add(te);
TD td = new TD() { Code = "Test",Message = "Yusuf",StartDate = DateTime.Now,EndDate = DateTime.Now,CTs = list,TEs = list2 };
List<TD> list3 = new List<TD>();
list3.Add(td);
Stopwatch stopwatch5 = new Stopwatch();
stopwatch5.Start();
string str = String.Empty;
using (MemoryStream stream = new MemoryStream())
{
byte[] data = Serialize(list3);
XmlDocument doc = new XmlDocument();
string xml = Encoding.UTF8.GetString(data); <--SHOULD CREATE XML
doc.LoadXml(xml);
// str = Convert.ToBase64String(stream.GetBuffer(),0,(int)stream.Length);
}
stopwatch5.Stop();
Console.WriteLine(((double)(stopwatch5.Elapsed.TotalMilliseconds * 1000000) / 1000000).ToString("0.00 ns"));
Console.Read();
}
public static byte[] Serialize(List<TD> tData) {
using(var ms = new MemoryStream()) {
ProtoBuf.Serializer.Serialize(ms,tData);
return ms.ToArray();
}
}
public static List<TD> Deserialize(byte[] tData) {
using(var ms = new MemoryStream(tData)) {
return ProtoBuf.Serializer.Deserialize<List<TD>>(ms);
}
}
it should create xml as a result of " string xml = Encoding.UTF8.GetString(data);". But doesn't. How can i produxe xml result?
Protocol buffers doesn't serialize objects to XML.
It produces binary data. And it has its own set of attributes.
Check this answer
Is Protobuf-net's serialization/deserialization faster than XML ? Yes, by far.
However XmlSerializer is fast enough for most of the tasks.
What you should remind when using it though, is:
XmlSerializer instance is generating code on the fly and compile this code into an assembly.
This generated assembly is then used to serialize and deserialize your objects really fast.
So you should cache instances of XmlSerializer (and avoid recreating them)
you could add a warm up by calling Serialize and Deserialize in order to initialize inner objects and jit them.
You could even go further by generating the auto-generated assembly by yourself, but then you should remember to regenerate each time you change the objects (It can be automated with a MsBuild Task).
You can also look further optimizations:
On Stack Overflow
With Sgen
You can only have one tag at the root level of your xml. So either TD cannot be a list, or you must have an outer tag around the List. This code works
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
TD td = new TD()
{
Code = "Test",
Message = "Yusuf",
StartDate = DateTime.Now,
EndDate = DateTime.Now,
CTs = new List<CT>() {
new CT() { Foo = 1},
new CT() { Foo = 2},
new CT() { Foo = 3},
},
TEs = new List<TE>() {
new TE() { Bar = 1},
new TE() { Bar = 2},
new TE() { Bar = 3},
}
};
using (MemoryStream stream = new MemoryStream())
{
byte[] data = Serialize(td);
XmlDocument doc = new XmlDocument();
string xml = Encoding.UTF8.GetString(data);
doc.LoadXml(xml);
// str = Convert.ToBase64String(stream.GetBuffer(),0,(int)stream.Length);
}
}
public static byte[] Serialize(TD tData)
{
using (var ms = new MemoryStream())
{
XmlSerializer serializer = new XmlSerializer(typeof(TD));
serializer.Serialize(ms, tData);
return ms.ToArray();
}
}
public static TD Deserialize(byte[] tData)
{
using (var ms = new MemoryStream(tData))
{
XmlSerializer xs = new XmlSerializer(typeof(TD));
return (TD)xs.Deserialize(ms);
}
}
}
[XmlRoot("CT")]
public class CT
{
[XmlElement(ElementName = "Foo", Order = 1)]
public int Foo { get; set; }
}
[XmlRoot("TE")]
public class TE
{
[XmlElement(ElementName = "Bar", Order = 1)]
public int Bar { get; set; }
}
[XmlRoot("TD")]
public class TD
{
[XmlElement(ElementName = "CTs", Order = 1)]
public List<CT> CTs { get; set; }
[XmlElement(ElementName = "TEs", Order = 2)]
public List<TE> TEs { get; set; }
[XmlElement(ElementName = "Code", Order = 3)]
public string Code { get; set; }
[XmlElement(ElementName = "Message", Order = 4)]
public string Message { get; set; }
[XmlElement(ElementName = "StartDate", Order = 5)]
public DateTime StartDate { get; set; }
[XmlElement(ElementName = "EndDate", Order = 6)]
public DateTime EndDate { get; set; }
}
}
​

Deserialize complex object using custom XmlSerialization

I have written following sample code to save a slightly complex object FamilyTreeFile to XML and restore it back to original form.
public class XmlSerializationTest
{
const string FileName = #"FamilyTree.xml";
public void Run()
{
var rootMember = new Member() { Name = "Johny", Parent = null };
var member1 = new Member() { Name = "Andy", Parent = rootMember };
var member2 = new Member() { Name = "Adam", Parent = rootMember };
var member3 = new Member() { Name = "Andrew", Parent = rootMember };
var member4 = new Member() { Name = "Davis", Parent = member2 };
var member5 = new Member() { Name = "Simon", Parent = member4 };
rootMember.FamilyTree = new GenericCollection();
rootMember.FamilyTree.Add(member1);
rootMember.FamilyTree.Add(member2);
rootMember.FamilyTree.Add(member3);
member2.FamilyTree = new GenericCollection();
member2.FamilyTree.Add(member4);
member4.FamilyTree = new GenericCollection();
member4.FamilyTree.Add(member5);
var familyTree = new GenericCollection() { rootMember };
IFamilyTreeFile file = new FamilyTreeFile()
{
FamilyTree = familyTree
};
Serialize(file);
file = Deserialize();
}
public void Serialize(IFamilyTreeFile obj)
{
var xmlSerializer = new XmlSerializer(typeof(FamilyTreeFile));
using (TextWriter writer = new StreamWriter(FileName))
{
xmlSerializer.Serialize(writer, obj);
}
}
public IFamilyTreeFile Deserialize()
{
XmlSerializer serializer = new XmlSerializer(typeof(FamilyTreeFile));
using (Stream stream = File.Open(FileName, FileMode.Open))
{
return (IFamilyTreeFile)serializer.Deserialize(stream);
}
}
}
public interface IMember
{
string Name { get; set; }
IMember Parent { get; set; }
GenericCollection FamilyTree { get; set; }
}
[Serializable]
public class Member : IMember
{
[XmlAttribute]
public string Name { get; set; }
[XmlIgnore]
public IMember Parent { get; set; }
public GenericCollection FamilyTree { get; set; }
public Member()
{
//FamilyTree = new GenericCollection();
}
}
[Serializable]
public class GenericCollection : List<IMember>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
if (reader.Name == "FamilyTree")
{
do
{
reader.Read();
if (reader.Name == "Member" && reader.IsStartElement())
{
Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.Name == reader.Name)
.FirstOrDefault();
if (type != null)
{
var xmlSerializer = new XmlSerializer(type);
var member = (IMember)xmlSerializer.Deserialize(reader);
this.Add(member);
}
}
if (reader.Name == "FamilyTree" && reader.NodeType == System.Xml.XmlNodeType.EndElement)
break;
}
while (!reader.EOF);
}
}
public void WriteXml(XmlWriter writer)
{
foreach (IMember rule in this)
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add(String.Empty, String.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(rule.GetType());
xmlSerializer.Serialize(writer, rule, namespaces);
}
}
}
public interface IFamilyTreeFile
{
GenericCollection FamilyTree { get; set; }
}
public class FamilyTreeFile : IFamilyTreeFile
{
public GenericCollection FamilyTree { get; set; }
}
The code sample is generating the following XML file which is exactly as per my needs but i am unable to read it back using ReadXml method.
<?xml version="1.0" encoding="utf-8"?>
<FamilyTreeFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FamilyTree>
<Member Name="Johny">
<FamilyTree>
<Member Name="Andy" />
<Member Name="Adam">
<FamilyTree>
<Member Name="Davis">
<FamilyTree>
<Member Name="Simon" />
</FamilyTree>
</Member>
</FamilyTree>
</Member>
<Member Name="Andrew" />
</FamilyTree>
</Member>
</FamilyTree>
</FamilyTreeFile>
I need help in how can i restore it back efficiently?
ADDED
Upon adding new collection Notes in IMember
public interface IMember
{
string Name { get; set; }
IMember Parent { get; set; }
GenericCollection FamilyTree { get; set; }
List<Note> Notes { get; set; }
}
[Serializable]
public class Note
{
[XmlAttribute]
public string Text { get; set; }
}
Implementing this property in Member class
[XmlArray("Notes")]
public List<Note> Notes { get; set; }
I am unable to deserialize Notes information at this line.
var member = (IMember)xmlSerializer.Deserialize(reader);
Isn't there any simple way to deserialize using XmlSerializer or any framework which handles everything itself?
Here is a working version of the GenericCollection.ReadXml method for you:
public void ReadXml(XmlReader reader)
{
// no need to advace upfront so MoveToContent was taken out (would
// mess with subsequent inner deserializations anyway)
// very important: there may be no members, so check IsEmptyElement
if (reader.Name == "FamilyTree" && !reader.IsEmptyElement)
{
do
{
if (reader.Name == "Member" && reader.IsStartElement())
{
Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.Name == reader.Name)
.FirstOrDefault();
if (type != null)
{
var xmlSerializer = new XmlSerializer(type);
var member = (IMember) xmlSerializer.Deserialize(reader);
this.Add(member);
}
continue; // to omit .Read because Deserialize did already
// advance us to next element
}
if (reader.Name == "FamilyTree" && reader.NodeType == XmlNodeType.EndElement)
break;
reader.Read();
} while (!reader.EOF);
}
}
What you will most propably have missed out on in your version, was the fact that every call to a method of the XmlReader like Read...() or Move...() does advance it's reading position. The inner deserialization of member objects does the same.
Keeping this in mind, it should become clear that you simply cannot always issue a Read() at the beginning of the loop, but only at the very end. This way you can skip it with the continue keyword, in case some other code in the loop body (like Deserialize() in our case) did already advance the XmlReader. Same applies to MoveToContent() at the beginning of your version of the method. What I initially did miss out on too, is the fact that the collection of members can be empty. In that case the deserialization of GenericCollection has to be omitted completely, as (again) not to mess up the reader.
While this does deserialize the object instances and adds them to their respective lists, the references (the Parent field of the Member class in this example) are not reconstructed. Here is where things get tricky: A reference is esentially a memory adress. Being that, there is no point in serializing it's value and deserializing it back again. Because the objects will most propably reside in another memory location now, the deserialized address would be entirely wrong.
There are basically two ways to solve this:
The serialized objects could be constructed in a manner that automatically creates those references, when the objects are constucted or glued together. This way there is simply no serialization and deserialization needed. The drawback is: This is only possible for references that can be obtained in this manner (is the case in the current example)
Every object that can be target of a reference cold be extended by an identifier field, quite similar to a primary key in a database. This identifier (for example a guid) is then to be serialized and deserialized. Every referece field (the Parent field of the Member class in this example) is to be serialized as identifier value of the object it references (could be done by adding a helper field ParentID, which is set automatically by the setter of the Parent filed). When everything is deserialized, these references have to be reconstructed by walking the entire tree of objects. On the plus side, this enables one to reconstruct arbitrary references. But one has to be aware of this adding some real complexity to the code.
First approach could be done by:
Changing this in your Run() function...
var rootMember = new Member() { Name = "Johny"};
var member1 = new Member() { Name = "Andy" };
var member2 = new Member() { Name = "Adam" };
var member3 = new Member() { Name = "Andrew" };
var member4 = new Member() { Name = "Davis" };
var member5 = new Member() { Name = "Simon" };
...change property FamilyTree of class Member to this...
public GenericCollection FamilyTree
{
get { return _FamilyTree; }
set
{
_FamilyTree = value;
_FamilyTree.Owner = this;
}
}
... and insert this into class GenericCollection
private IMember _Owner;
public IMember Owner
{
get { return _Owner; }
set
{
_Owner = value;
foreach (var member in this)
{
member.Parent = value;
}
}
}
public void Add(IMember item)
{
item.Parent = Owner;
base.Add(item);
}
The second approach is implemented in the following small console application:
class Program
{
public static string FileName = #"FamilyTree.xml";
static void Main(string[] args)
{
// make some members
var rootMember = new Member() { Name = "Johny" };
var member1 = new Member() { Name = "Andy" };
var member2 = new Member() { Name = "Adam" };
var member3 = new Member() { Name = "Andrew" };
var member4 = new Member() { Name = "Davis" };
var member5 = new Member() { Name = "Simon" };
// construct some arbitrary references between them
member1.Reference = member4;
member3.Reference = member1;
member5.Reference = member2;
// let member 3 have some notes
member3.Notes = new List<Note>();
member3.Notes.Add(new Note() { Text = "note1" });
member3.Notes.Add(new Note() { Text = "note2" });
// add all of the to the family tree
rootMember.FamilyTree.Add(member1);
rootMember.FamilyTree.Add(member2);
rootMember.FamilyTree.Add(member3);
member2.FamilyTree.Add(member4);
member4.FamilyTree.Add(member5);
var familyTree = new GenericCollection() { rootMember };
IFamilyTreeFile file = new FamilyTreeFile()
{
FamilyTree = familyTree
};
Console.WriteLine("--- input ---");
Serialize(file);
PrintTree(file.FamilyTree, 0);
Console.WriteLine();
Console.WriteLine("--- output ---");
file = Deserialize();
file.FamilyTree.RebuildReferences(file.FamilyTree); // this is where the refereces
// are put together again after deserializing the object tree.
PrintTree(file.FamilyTree, 0);
Console.ReadLine();
}
private static void PrintTree(GenericCollection c, int indent)
{
foreach (var member in c)
{
string line = member.Name.PadLeft(indent, ' ');
if (member.Reference != null)
{
line += " (Ref: " + member.Reference.Name + ")";
if (member.Notes != null && member.Notes.Count > 0)
{
line += " (Notes: ";
foreach (var note in member.Notes)
{
line += note.Text + ",";
}
line += ")";
}
}
Console.WriteLine(line);
PrintTree(member.FamilyTree, indent + 4);
}
}
public static void Serialize(IFamilyTreeFile obj)
{
var xmlSerializer = new XmlSerializer(typeof(FamilyTreeFile));
using (TextWriter writer = new StreamWriter(FileName))
{
xmlSerializer.Serialize(writer, obj);
}
}
public static IFamilyTreeFile Deserialize()
{
XmlSerializer serializer = new XmlSerializer(typeof(FamilyTreeFile));
using (Stream stream = File.Open(FileName, FileMode.Open))
{
return (IFamilyTreeFile)serializer.Deserialize(stream);
}
}
}
public interface IMember
{
Guid ID { get; set; }
string Name { get; set; }
IMember Reference { get; set; }
Guid ReferenceID { get; set; }
GenericCollection FamilyTree { get; set; }
List<Note> Notes { get; set; }
void RebuildReferences(GenericCollection in_Root);
}
[Serializable]
public class Member : IMember
{
private GenericCollection _FamilyTree;
private IMember _Reference;
[XmlAttribute]
public Guid ID { get; set; }
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public Guid ReferenceID { get; set; }
[XmlIgnore]
public IMember Reference
{
get { return _Reference; }
set
{
ReferenceID = value.ID;
_Reference = value;
}
}
[XmlArray("Notes")]
public List<Note> Notes { get; set; }
public GenericCollection FamilyTree
{
get { return _FamilyTree; }
set
{
_FamilyTree = value;
_FamilyTree.Owner = this;
}
}
public Member()
{
ID = Guid.NewGuid();
FamilyTree = new GenericCollection();
}
public void RebuildReferences(GenericCollection in_Root)
{
if (!ReferenceID.Equals(Guid.Empty))
Reference = in_Root.FindMember(ReferenceID);
FamilyTree.RebuildReferences(in_Root);
}
}
[Serializable]
public class Note
{
[XmlAttribute]
public string Text { get; set; }
}
[Serializable]
public class GenericCollection : List<IMember>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
private IMember _Owner;
public IMember Owner
{
get { return _Owner; }
set
{
_Owner = value;
}
}
public void Add(IMember item)
{
base.Add(item);
}
public void ReadXml(XmlReader reader)
{
// no need to advace upfront so MoveToContent was taken out (would
// mess with subsequent inner deserializations anyway)
// very important: there may be no members, so check IsEmptyElement
if (reader.Name == "FamilyTree" && !reader.IsEmptyElement)
{
do
{
if (reader.Name == "Member" && reader.IsStartElement())
{
Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.Name == reader.Name)
.FirstOrDefault();
if (type != null)
{
var xmlSerializer = new XmlSerializer(type);
var member = (IMember)xmlSerializer.Deserialize(reader);
this.Add(member);
}
continue; // to omit .Read because Deserialize did already
// advance us to next element
}
if (reader.Name == "FamilyTree" && reader.NodeType == XmlNodeType.EndElement)
break;
reader.Read();
} while (!reader.EOF);
}
}
public void WriteXml(XmlWriter writer)
{
foreach (IMember rule in this)
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add(String.Empty, String.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(rule.GetType());
xmlSerializer.Serialize(writer, rule, namespaces);
}
}
public void RebuildReferences(GenericCollection in_Root)
{
foreach (IMember meber in this)
{
meber.RebuildReferences(in_Root);
}
}
public IMember FindMember(Guid in_ID)
{
IMember FoundMember = null;
foreach (IMember member in this)
{
if (member.ID.Equals(in_ID))
return member;
FoundMember = member.FamilyTree.FindMember(in_ID);
if (FoundMember != null)
return FoundMember;
}
return null;
}
}
public interface IFamilyTreeFile
{
GenericCollection FamilyTree { get; set; }
}
public class FamilyTreeFile : IFamilyTreeFile
{
public GenericCollection FamilyTree { get; set; }
}
A proof of concept for your addition to the original question is disclosed within this second example.

Unable to serialize object into XML file

I'm trying to save a dictionary of Matrix into an Xml file.
My Matrix class attributes are :
public class Matrix
{
public int Lines { get; set; }
public int Columns { get; set; }
public double[,] Elements { get; set; }
public string name { get; set; }
}
After many attempts, I wrote this :
string fileName = dlg.FileName;
Stream writer = new FileStream(fileName,FileMode.Create);
foreach (KeyValuePair<String, Matrix> matrice in CalMat.Calculatrice.listMatrix)
{
XmlSerializer x = new XmlSerializer(matrice.GetType());
x.Serialize(writer, matrice);
}
writer.Close();
If i run this code with one matrix, the file is created, but i only have this sentence written :
<?xml version="1.0"?><KeyValuePairOfStringMatrix xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" /><?xml version="1.0"?>
I think my code is missing something but I don't know what. A write method, I guess.
Thank you for your time!
I don't think the default KeyValuePair is serializable,
try building your own KeyValuePair class:
[Serializable]
[XmlType(TypeName="MyTypeName")]
public struct KeyValuePair<T1, T2>
{
public T1 Key { get; set; }
public T2 Value { get; set; }
}
Using BinaryFormatter this is the code:
[Serializable] // mark with Serializable
public class Matrix
{
public Matrix(string name, int lines, int columns)
{
Name = name;
Lines = lines;
Columns = columns;
Elements = new double[Lines, Columns];
}
public int Lines { get; set; }
public int Columns { get; set; }
public double[,] Elements { get; set; }
public string Name { get; set; }
}
public static void Main()
{
var path = #"D:\serialize.data"; // use the path that you want
// this is an example collection
var listMatrix = new Dictionary<string, Matrix>();
listMatrix.Add("matrix_1", new Matrix("Matrix 1", 1, 2));
listMatrix.Add("matrix_2", new Matrix("Matrix 2", 2, 2));
// Serialization
var stream = new FileStream(path, FileMode.Create);
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(stream, listMatrix);
stream.Close();
// Deserialization
stream = new FileStream(path, FileMode.Open);
var result = (Dictionary<string, Matrix>)binaryFormatter.Deserialize(stream);
stream.Close();
}
Using XmlSerializer this is the code:
// I implement my custom KeyValuePair to serialize (because XmlSerializer can not serialize the .net KeyValuePair)
public struct CustomKeyValuePair<T1, T2>
{
public CustomKeyValuePair(T1 key, T2 value): this()
{
Key = key;
Value = value;
}
public T1 Key { get; set; }
public T2 Value { get; set; }
// here I specify how is the cast
public static explicit operator CustomKeyValuePair<T1, T2>(KeyValuePair<T1, T2> keyValuePair)
{
return new CustomKeyValuePair<T1, T2>(keyValuePair.Key, keyValuePair.Value);
}
}
// Matrix class used to Serialize with XmlSerailzer
public class Matrix
{
public Matrix() { } // need a default constructor
public Matrix(string name, int lines, int columns)
{
Name = name;
Lines = lines;
Columns = columns;
Elements = new double[Columns][];
for (int i = 0; i < Elements.Length; i++)
{
Elements[i] = new double[Columns];
}
}
public int Lines { get; set; }
public int Columns { get; set; }
public double[][] Elements { get; set; } // I use double[][] because XmlSerialzer can not serialize a two-dimensional array (double[,])
public string Name { get; set; }
}
public static void Main()
{
var path = #"D:\serialize.data"; // use the path that you want
// this is an example collection
var listMatrix = new Dictionary<string, Matrix>();
listMatrix.Add("matrix_1", new Matrix("Matrix 1", 1, 2));
listMatrix.Add("matrix_2", new Matrix("Matrix 2", 2, 2));
// Serialization
var stream = new FileStream(path, FileMode.Create);
var xmlSerializer = new XmlSerializer(typeof(CustomKeyValuePair<string, Matrix>[]));
var aux = listMatrix.Select(keyValuePair => (CustomKeyValuePair<string, Matrix>) keyValuePair).ToArray();
xmlSerializer.Serialize(stream, aux); // I serialize an array to make easy the deserailizer
stream.Close();
// Deserialization
stream = new FileStream(path, FileMode.Open);
var result = (CustomKeyValuePair<string, Matrix>[])xmlSerializer.Deserialize(stream);
stream.Close();
}

Categories