Can be used for complex datasaving (useful for OOP)
There are a lot of ways to save your data (ie. your World, Maps, Configurations, DataBases). One of them is Serialization. I’ll explain the usage of it with the .NET Framework 2.0 (and C#). It was possible in C++ before, but not that easy. Now you can achieve it with only a few lines of code.
A class / structure will be converted to a stream. Streams for example can be saved in files, transmitted via TCP/IP and more and can contain every type of data (string, integer, arrays). This file can be read in and can be transformed back to class by Deserialization. Therefor you need to have that class / structure in your program, otherwise it will be a stream with useless information (you can read in of course with some tricks, but it wont be that easy).
There are basically two types of serialization that can be used :
XML Serialization has one big advantage. You can edit the files without the program, as the output file can be opened with a simple text editor.
The requirements for a class to be XML-serializable :
public;
public parameterless constructor, which the XmlSerializer can invoke when deserializing the class from an Xml file;
public, or have public read/write properties;
The following lines must be added to the beginning of your code:
using System.Xml.Serialization; // Xml Serialization Methods using System.IO; // File System Methods
This will enable you to use classes of these namespaces directly without writing the namespace structure before them.
public class MyWorld { public MyWorld(){} // Constructor Function of MyWorld // Some Variables public int ID; public string Name; public string Music; // Here could be some arrays with our worldobjects // and their positions }
Alternatively, you could put private fields with public properties, like so :
public class MyWorld { public MyWorld(){} // Constructor Function of MyWorld // Some Variables private int id private string name; private string music; // Public properties public int ID { get { return id; } set { id = value; } } public string Name { get { return name; } set { name = value; } } public string Music { get { return music; } set { music = value; } } }
The rest of the tutorial stays the same, whatever method was used. The properties-using one is cleaner and is the one recommended for C# 2.0/2005. Custom attributes on properties can be used to format how the content will serialize as well. See this link for more information.
When our program starts, a world must be initialized. We write a function for that:
MyWorld TestWorld; // Now it is not a local variable of InitWorld, so we can access it from other functions too. public void InitWorld() { // Create a new object of type MyWorld TestWorld = new MyWorld(); // Fill in some Values TestWorld.ID = 1; TestWorld.Name = "Ancient Underground World"; TestWorld.Music = "mymusic.mp3"; }
But now we want to save our world. This is achieved with the following lines:
public void SaveWorld() { // Create an serializer, which takes over the object-type MyWorld XmlSerializer serializer = new XmlSerializer(typeof(MyWorld)); // Create an stream-object and save it as a file StreamWriter writer = File.CreateText(@"C:\MyWorld.xml"); // This function does it all for us serializer.Serialize(writer, TestWorld); // Close the file writer.Close(); }
Now, in our main function (which is called when the Application is executed):
InitWorld(); SaveWorld();
A file named MyWorld.xml will be created in our C:\ root directory. It will contain the following data:
<?xml version="1.0" encoding="utf-8"?> <MyWorld xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ID>1</ID> <Name>Ancient Underground World</Name> <Music>mymusic.mp3</Music> </MyWorld>
There you can see: All our data we added to the class before with InitWorld are saved in that XML file now. You can easily change them and if we load them in again they will contain your modified data. And here comes the function to read in our file.
public void LoadWorld() { // Again, create a serializer, which takes over the object-type MyWorld XmlSerializer serializer = new XmlSerializer(typeof(MyWorld)); // Create a stream-object and open the file StreamReader reader = File.OpenText(@"C:\MyWorld.xml"); // This is the fuction which does all for us again. TestWorld = serializer.Deserialize(reader) as MyWorld; }
Annotation: A serializer always return an Object. But TestWorld is an object of the type MyWorld. So with an “as” statement (or with a cast using (MyWorld)) the Object is casted to the right type.
One problem of the XML serialization is : everybody can see and edit your data. One avenue to prevent this would be to encrypt or encode the XML, but .Net provides another way of saving our data in a more complex and size-effective file which can’t be edited that easily. It’s called Binary Serialization and we only need a BinaryFormatter for that.
That said... To use it, first we have to modify our ‘usings’ :
//using System.Xml.Serialization; // Serialization Methods (XML) using System.IO; // File System Methods using System.Runtime.Serialization.Formatters.Binary; // Serialization Methods (Binary)
Another thing must be added to the MyWorld class for it to be serializable using a BinaryFormatter : the [Serializable] attribute. This attribute tells all IFormatter implementors (like BinaryFormatter) that he’s allowed to serialize this class.
[Serializable] public class MyWorld() { ... }
Now we have to change our SaveWorld function...
public void SaveWorld() { // Create a binary formatter IFormatter bformatterf = new BinaryFormatter(); // Create a stream-object and save it as a file StreamWriter writer = File.CreateText(@"C:\MyWorld.dat"); // This function does it all for us bformatter.Serialize(writer.BaseStream, database); // Close the file writer.Close(); }
...and of course our LoadWorld function.
public void LoadWorld() { // Again, create a binary formatter IFormatter bformatter = new BinaryFormatter(); // Create a stream-object and open the file StreamReader reader = File.OpenText(@"D:\MyWorld.dat"); // This is the fuction which does all for us again. TestWorld = (MyWorld) bformatter.Deserialize(reader.BaseStream); }
This was all! Now we can take a look into that created DAT file (with the editor):
���� ERuntime, Version=1.0.1936.29141, Culture=neutral, PublicKeyToken=null
... are the first few chars (Try it, i cant add the rest of the file as it contains controlcharacters like backspace etc and they are NOT good for the Wiki). You can see that it is not very useful anymore. Of course, with some work you are able to extract information out of that, but its not that easy anymore (especially for integers) and for now that should fit. Now you have your own file format done. Congratulations!
As a note : You must serialize and deserialize a binary-formatted file in the same Assembly name. A serialized piece of data caries its Assembly name, type and version, which will conflict if you try to load it in a different code base. This happens even if the data structure/class/variable is the same, as the Assembly is different. To work around this, you must implement ISerializable and forge the Assembly name.
Here’s an example of a ready-to-use serializer, along with its deserializing counterpart. Its usage is fairly simple:
MyWorld world = GetWorld(); bool serializationSuccessful = Data.Serializer.Serialize<MyWorld>(world, "worldsave.dat"); // ... world = Data.Serializer.Deserialize<MyWorld>("worldsave.dat");
And here’s the code:
using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization; namespace Data { internal static class Serializer { public static bool Serialize<T>(T data, string filePath) { bool error = false; // Using a file stream. FileStream fileStream = new FileStream(filePath, FileMode.Create); // Construct a BinaryFormatter and use it to serialize the data to the stream. BinaryFormatter formatter = new BinaryFormatter(); try { formatter.Serialize(fileStream, (T)data); } catch (SerializationException e) { Console.WriteLine("Serializer: failed to serialize \"{0}\". Reason: {1}", filePath, e.Message); error = true; } finally { fileStream.Close(); } return !error; } public static T Deserialize<T>(string filePath) { T data = default(T); // If the file exists... if (File.Exists(filePath)) { // Open the file containing the data that you want to deserialize. FileStream fileStream = new FileStream(filePath, FileMode.Open); try { BinaryFormatter formatter = new BinaryFormatter(); // Deserializing data. data = (T)formatter.Deserialize(fileStream); } catch (SerializationException e) { Console.WriteLine("Serializer: failed to deserialize \"{0}\". Reason: {1}", filePath, e.Message); return default(T); } finally { fileStream.Close(); } } return data; } } }
EagleEye’s serialize/deserialize VB code... Feel free to use it if you’re a VB guy.
Imports System.IO Imports System.Data Imports System.Runtime.Serialization Imports System.Runtime.Serialization.Formatters.Binary Public Module DataUtils Public Function SerialData(ByVal Dta As Object) As Byte() Dim ms As New MemoryStream Dim formatter As IFormatter = New BinaryFormatter Try formatter.Serialize(ms, Dta) ms.Seek(0, SeekOrigin.Begin) Return ms.ToArray Catch ex As Exception Return ms.ToArray End Try End Function Public Function DeserialData(ByVal Bytes As Byte()) As Object Try Dim ms As New MemoryStream(Bytes) Dim Formatter As IFormatter = New BinaryFormatter Return Formatter.Deserialize(ms) Catch ex As Exception Return Nothing End Try End Function End Module