Binary Serialization, I think I almost have it working - c#

I've been following a guide on Binary Serialization (this one here: http://www.codeproject.com/Articles/1789/Object-Serialization-using-C) and I think I finally almost have it working. When I save the file is created, but when I try to load, nothing is loaded. I feel like I'm close to getting this working. Any advice would be appreciated. Here's the code:
Save/Load class
[Serializable()]
public class SaveLoad : ISerializable
{
public int GameDay = Date.GameDay;
public List<Adventurer> Adventurers = FormMain.AdventurerManager.AdventurerList;
public SaveLoad()
{
GameDay = 0;
Adventurers = null;
}
public SaveLoad(SerializationInfo info, StreamingContext ctxt)
{
GameDay = (int)info.GetValue("Date", typeof(int));
Adventurers = (List<Adventurer>)info.GetValue("Adventurers", typeof(List<Adventurer>));
}
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
info.AddValue("Date", GameDay);
info.AddValue("Adventurers", Adventurers);
}
}
Save/Load methods:
void btnSaveGame_Click(object sender, EventArgs e)
{
SaveLoad save = new SaveLoad();
Stream stream = File.Open("SaveGame.osl", FileMode.Create);
BinaryFormatter bformatter = new BinaryFormatter();
bformatter.Serialize(stream, save);
stream.Close();
}
void btnLoadGame_Click(object sender, EventArgs e)
{
SaveLoad load = new SaveLoad();
Stream stream = File.Open("SaveGame.osl", FileMode.Open);
BinaryFormatter bformatter = new BinaryFormatter();
load = (SaveLoad)bformatter.Deserialize(stream);
stream.Close();
Date.CalculateDate();
this.Visible = false;
((FormMain)(this.ParentForm)).ControlMainScreen.Visible = true;
}

I think you may just have an initialization timing issue.
Try either moving you initialization of the GameDay and Adventurers to the constructor or get rid of the nulling them out in the constructor. Once I did the following, the code pretty much works:
public SaveLoad()
{
//GameDay = null;
//Adventurers = null;
}
Note I had to also make sure that the Adventurer class had the Serialization attribute.
Here is the code with the serialization that works for me (I had to create my own Adventurer class and I replaced the date with a string since I couldn't figure out what you were doing with it or where it was coming from. I also populated the adventurers list with some dummy data and commented out anything to do with the form stuff that I also didn't have information on.
[Serializable()]
public class SaveLoad : ISerializable
{
public string GameDay = null;
public List<Adventurer> Adventurers = null;
//FormMain.AdventurerManager.AdventurerList;
public SaveLoad()
{
GameDay = "Date";
Adventurers = new List<Adventurer>() { new Adventurer { Name = "a1", Type = "t1" }, new Adventurer { Name = "a1", Type = "t1" } }; ;
}
public SaveLoad(SerializationInfo info, StreamingContext ctxt)
{
GameDay = (string)info.GetValue("Date", typeof(string));
Adventurers = (List<Adventurer>)info.GetValue("Adventurers", typeof(List<Adventurer>));
}
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
info.AddValue("Date", GameDay);
info.AddValue("Adventurers", Adventurers);
}
}
[Serializable()]
public class Adventurer
{
public string Name { get; set; }
public string Type { get; set; }
}
private void btnLoadGame_Click(object sender, EventArgs e)
{
SaveLoad sl = new SaveLoad();
Stream stream = File.Open("SaveGame.osl", FileMode.Open);
BinaryFormatter bformatter = new BinaryFormatter();
sl = (SaveLoad)bformatter.Deserialize(stream);
stream.Close();
MessageBox.Show(sl.Adventurers.Count.ToString());
//Date.CalculateDate();
//this.Visible = false;
//((Form1)(this.ParentForm)).ControlMainScreen.Visible = true;
}
private void btnSaveGame_Click(object sender, EventArgs e)
{
SaveLoad sl = new SaveLoad();
Stream stream = File.Open("SaveGame.osl", FileMode.Create);
BinaryFormatter bformatter = new BinaryFormatter();
bformatter.Serialize(stream, sl);
stream.Close();
}

EDIT
#JasonHaley is right, you have a timing/initialization issue.
During your Load button click event your defining a new SaveLoad called "Load"
This creates a reference to the FormMain.AdventurerManager.AdventurerList
But during deserialization this reference is destroyed by the object that was serialized to disk (the other list of adventurers) and is now a DIFFERENT list of adventurers from the one defined in your FormMain.AdventurerManager.AdventurerList
You need to load into that list specifically...
void btnLoadGame_Click(object sender, EventArgs e)
{
Stream stream = File.Open("SaveGame.osl", FileMode.Open);
BinaryFormatter bformatter = new BinaryFormatter();
SaveLoad load = (SaveLoad)bformatter.Deserialize(stream);
// ***********************************
FormMain.AdventurerManager.AdventurerList = SaveLoad.Adventurers
// ***********************************
stream.Close();
Date.CalculateDate();
this.Visible = false;
((FormMain)(this.ParentForm)).ControlMainScreen.Visible = true;
}

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.

Deserialize object from binary file

Hey guys I am working on a program that will take a person's name and comment and then serialize it to a binary file. It must also be able to deserialize and load the data to the form. I need it to be able to go up and down the list of files(names and comments) by using buttons on the form.
In my code I have it separated into a class that builds the object and the code for the form, and no i'm not allowed to separate it. (Just in case anyone thought that'd work)
the form code:
private void btnAdd_Click(object sender, EventArgs e)
{
bool blnValid = true;
if (string.IsNullOrWhiteSpace(txtName.Text))
{
MessageBox.Show("Please enter a valid name");
}
if (string.IsNullOrWhiteSpace(txtComment.Text))
{
MessageBox.Show("Please enter a valid comment");
blnValid = false;
}
if (blnValid)
{
//create class instance and assign property values
//set file name
DateTime CurrentDate = DateTime.Now;
string strFileName;
strFileName = CurrentDate.ToString("dd-MM-yy-hh-mm-ss") + ".bin";
// serialize object to binary file
MessageBox.Show(strFileName);
Enquiry newEnquiry = new Enquiry();
newEnquiry.Name = txtName.Text;
newEnquiry.Comment = txtComment.Text;
newEnquiry.DOB = dteDOB.Value;
newEnquiry.WriteToFile(strFileName, newEnquiry);
}
}
private void btnLoad_Click(object sender, EventArgs e)
{
}
private void btnPrevious_Click(object sender, EventArgs e)
{
}
and the class code:
[Serializable]
class Enquiry
{
//stores the various values into the enquiry class.
public string Name { get; set; }
public DateTime DOB { get; set; }
public string Comment { get; set; }
//creates the file, if there isn't one, writes the file, and
//disables sharing while the program is active. It also formats
//the file into binary
public void WriteToFile(string strFileName, Enquiry newEnquiry)
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(strFileName, FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, newEnquiry);
stream.Close();
}
// this loads the files and translates them to plain text
public void ReadFromFile(string strFileName, Enquiry newEnquiry)
{
Stream stream = File.OpenRead(strFileName);
BinaryFormatter formatter = new BinaryFormatter();
newEnquiry = (Enquiry)formatter.Deserialize(stream);
stream.Close();
}
I don't know what you are asking, but if you need methods to serialize and deserialize, use these:
public static void BinarySerializeObject(string path, object obj)
{
using (StreamWriter streamWriter = new StreamWriter(path))
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
try
{
binaryFormatter.Serialize(streamWriter.BaseStream, obj);
}
catch (SerializationException ex)
{
throw new SerializationException(((object) ex).ToString() + "\n" + ex.Source);
}
}
}
public static object BinaryDeserializeObject(string path)
{
using (StreamReader streamReader = new StreamReader(path))
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
object obj;
try
{
obj = binaryFormatter.Deserialize(streamReader.BaseStream);
}
catch (SerializationException ex)
{
throw new SerializationException(((object) ex).ToString() + "\n" + ex.Source);
}
return obj;
}
}

How does marking classes as [Serializable] work in C#?

I made a class called MyData and marked it with [Serializable], but it doesn't seem to work...
Here's MyData.cs:
namespace Forms_Bot_Rebuild
{
[Serializable]
public class MyData
{
private String _Email;
private String _Password;
private String _WorldId;
public MyData(string email, string password, string worldid)
{
_Email = email;
_Password = password;
_WorldId = worldid;
}
public string Email
{ get { return _Email; } set { _Email = value; } }
public string Password
{ get { return _Password; } set { _Password = value; } }
public string WorldId
{ get { return _WorldId; } set { _WorldId = value; } }
}
}
Here's Form1.cs:
private void button1_Click(object sender, EventArgs e)
{
MyData data = new MyData(boxEmail.Text, boxPass.Text, boxWorldId.Text);
string objectNode = Serialize.ToJsonString(data);
File.WriteAllText(Application.StartupPath + #"\LoginData.json", objectNode);
}
private void button2_Click(object sender, EventArgs e)
{
object myData;
myData = Serialize.Deserialize<MyData>(new FileStream(Application.StartupPath + #"\LoginData.json", FileMode.Open));
boxEmail.Text = data.Email;
boxPass.Text = data.Password;
boxWorldId.Text = data.WorldId;
}
Here's Serialize.cs:
public class Serialize
{
public static StreamReader ToStreamReader(object o)
{
MemoryStream stream = new MemoryStream();
DataContractJsonSerializer ser = new DataContractJsonSerializer(o.GetType());
ser.WriteObject(stream, o);
stream.Position = 0;
StreamReader sr = new StreamReader(stream);
return sr;
}
public static string ToJsonString(object o)
{
MemoryStream stream = new MemoryStream();
DataContractJsonSerializer ser = new DataContractJsonSerializer(o.GetType());
ser.WriteObject(stream, o);
stream.Position = 0;
StreamReader sr = new StreamReader(stream);
return sr.ReadToEnd();
}
public static object Deserialize<T>(Stream stream)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
stream.Position = 0;
T t = (T)ser.ReadObject(stream);
return t;
}
}
Also, this code was given to me by a friend, so I asked him. He couldn't understand it too. I asked another friend, who, in my opinion, knew decent C#, but he couldn't fix it too.
An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in System.Runtime.Serialization.dll
Additional information: Expecting element 'root' from namespace ''.. Encountered 'None' with name '', namespace ''.
Any opinions?
You should use
[DataContract]
instead of
[Serializable]
and mark all the properties with
[DataMember]
(but funnily, on my PC works even with Serializable, only it serializes fields instead of properties... Mmmh I see someone else noticed it: http://pietschsoft.com/post/2008/02/27/NET-35-JSON-Serialization-using-the-DataContractJsonSerializer)
Note that Deserialize could have this signature:
public T Deserialize<T>(Stream stream)
and you should close streams after using them:
MyData myData;
using (var fs = new FileStream(Application.StartupPath + #"\LoginData.json""LoginData.json", FileMode.Open))
{
myData = (MyData)Serialize.Deserialize<MyData>(fs);
}
If instead you really want to know what you wrote in the title:
Each serialization class/method look for "its" serialization attributes (at this time there are at least three families of attributes in Microsoft .NET: [Serializable], the [Xml*] family, the [DataContract]. Plus if you use JSON.NET there are its [Json*] attributes. Stop. So depending on the class/method you want to use to serialize your data, you have to know which attributes it will look for.

Deserialisation doesn't rebuild dictionaries that are members of classes

I've got some serialisation code set up as follows:
static void SerialiseObject(Object o, String path)
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(path, FileMode.Create);
formatter.Serialize(stream, o);
stream.Close();
}
static Object DeserialiseObject(String path)
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
Object o = (Object)formatter.Deserialize(stream);
stream.Close();
return o;
}
And a class with the following member defined:
[Serializable]
public class CircuitModel
{
public Dictionary<String, Bus> Buses { protected set; get; }
...
}
I populate the Dictionary, and then the following code successfully serialises and deserialises the dictionary, with all Bus objects intact:
SerialiseObject(CircuitModel.Buses, "temp.bin");
Object o = DeserialiseObject("temp.bin");
But when I try to do the same for CircuitModel:
SerialiseObject(CircuitModel, "temp.bin");
Object o = DeserialiseObject("temp.bin");
CircuitModel.Buses has been initialised, but is empty.
I've also tried implementing serialisation with ISerializable (for the Bus and CircuitModel classes) and had exactly the same problem
Any idea as to why this would be happening?
I think you have something more sinister going on with your child collection because binary serialization of Dictionaries within classes does work just fine.
[TestFixture]
public class SerializeTest
{
[Test]
public void TestSer()
{
var parent = new Parent
{
Name = "Test"
};
parent.Children.Add("Child1", new Child {Name = "Child1"});
parent.Children.Add( "Child2", new Child { Name = "Child2" } );
SerialiseObject(parent, "test.bin");
var copy = DeserialiseObject("test.bin") as Parent;
Assert.IsNotNull(copy);
Assert.AreEqual(2, copy.Children.Count);
Assert.IsTrue(copy.Children.ContainsKey("Child1"));
Assert.AreEqual("Child1", copy.Children["Child1"].Name);
}
static void SerialiseObject( Object o, String path )
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream( path, FileMode.Create );
formatter.Serialize( stream, o );
stream.Close();
}
static Object DeserialiseObject( String path )
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream( path, FileMode.Open, FileAccess.Read );
Object o = (Object) formatter.Deserialize( stream );
stream.Close();
return o;
}
[Serializable]
private class Parent
{
public string Name { get; set; }
public Dictionary<string, Child> Children { get; protected set; }
public Parent()
{
Children = new Dictionary<string, Child>();
}
}
[Serializable]
private class Child
{
public string Name { get; set; }
}
}
The children deserialize with the parent and contain the details they were initialized with. I would check any code that is setting your Buses collection. My example just did it in the constructor of the parent class, but it may be possible that you have rogue code setting it after it's been deserialized?
Dictionaries are not serializable. Remove the dictionary if you need to serialize that data, and replace it by a list of a custom class that contains the data in the dictionary:
[Serializable]
public class BusItem
{
public string Name {get;set;}
public Bus Bus {get;set;}
}
Edit: I just found out you can actually serialize Dictionaries using the DataContractSerializer instead.
http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/
If you are talking about XML serialization, it might be because Dictionary is not serializable to XML. Look at Why isn't there an XML-serializable dictionary in .NET.

Windows Form XML Serialization Save Dialog

im stuck with this stupid form... heres my code. It saves it where the streamwriter wants it to but when i save it where the user wants via the savedialog box is creates the XML but doesnt put anything in it! Can someone have a look as it's starting to wind me up!
void SavebuttonClick(object sender, EventArgs e)
{
Stream myStream ;
SaveFileDialog savefile1 = new SaveFileDialog();
savefile1.Filter = "xml files |*.xml" ;
savefile1.FilterIndex = 2 ;
savefile1.RestoreDirectory = true ;
if(savefile1.ShowDialog() == DialogResult.OK)
{
if((myStream = savefile1.OpenFile()) != null)
{
Values v = new Values();
v.task1_name = this.task1_name.Text;
v.task1_desc = this.task1_desc.Text;
v.task1_date = this.task1_date.Value;
v.task1_time = this.task1_time.Value;
SaveValues(v);
}
myStream.Close();
}
}
This is the streamwriter...
public void SaveValues(Values v)
{
XmlSerializer serializer = new XmlSerializer(typeof(Values));
using(TextWriter textWriter = new StreamWriter(#"E:\TheFileYouWantToStore.xml"))
{
serializer.Serialize(textWriter, v);
}
...
}
EDIT:
public class Values
{
public string task1_name { get; set;}
public string task1_desc { get; set;}
public DateTime task1_date { get; set;}
public DateTime task1_time { get; set;}
}
I presume this is the code you meant, im fairly new to coding though mate :(
You must call textWriter.close(); after serialize. If you don't close writer it won't apply chenges to file.
By the way you are writing values to E:\TheFileYouWantToStore.xml. your save save method does not use users file.
public void SaveValues(Values v)
{
XmlSerializer serializer = new XmlSerializer(typeof(Values));
using(TextWriter textWriter = new StreamWriter(#"E:\TheFileYouWantToStore.xml"))
{
serializer.Serialize(textWriter, v);
textWriter.close();
}
...
}
EDIT:
if(savefile1.ShowDialog() == DialogResult.OK)
{
Values v = new Values();
v.task1_name = this.task1_name.Text;
v.task1_desc = this.task1_desc.Text;
v.task1_date = this.task1_date.Value;
v.task1_time = this.task1_time.Value;
SaveValues(savefile1.FileName, v);
}
-
public void SaveValues(string fileName, Values v)
{
XmlSerializer serializer = new XmlSerializer(typeof(Values));
using(TextWriter textWriter = new StreamWriter(fileName))
{
serializer.Serialize(textWriter, v);
textWriter.close();
}
...
}

Categories