Tool DevelopmentChapter 06: Binary Serialization, Worker Threads
Nick Prühs
5 Minute Review Session
• What is XML serialization?
• How is XML serialization controlled in .NET?
• What is XML Schema?
• What is the difference between simple and complex types in XML Schema?
• How do you define new simple types?
• How do you define new complex types?
• What is the major drawback of the INI file format?
• What is JSON?
• What is the main motivation behind YAML?
2 / 58
Assignment Solution #5
DEMO
3 / 58
Objectives
• To learn how to properly read and write binary files
• To understand how to use worker threads in order to create reactive UIs
4 / 58
Binary Serialization
• Sometimes you’ll want to serialize data in a format other than plain text
• As we have learned, this can basically be achieved using the BinaryReader and BinaryWriter classes in .NET
5 / 58
Writing Binary Data To Files
C#
6 / 58
// Collect information on the file to create.
FileInfo fileInfo = new FileInfo("newFile.dat");
// Create new file.
FileStream fileStream = fileInfo.Create();
// Create new binary writer.
BinaryWriter binaryWriter = new BinaryWriter(fileStream);
// Write data.
binaryWriter.Write(23);
binaryWriter.Write(true);
binaryWriter.Write(0.4f);
// Close file stream and release all resources.
binaryWriter.Close();
Reading From Binary Files
C#
7 / 58
// Collect information on the file to read from.
FileInfo fileInfo = new FileInfo("newFile.dat");
// Open file for reading.
FileStream fileStream = fileInfo.OpenRead();
// Create new binary reader.
BinaryReader binaryReader = new BinaryReader(fileStream);
// Read data.
int i = binaryReader.ReadInt32();
bool b = binaryReader.ReadBoolean();
float f = binaryReader.ReadSingle();
// Close file stream and release all resources.
binaryReader.Close();
Binary Serialization
• However, writing and reading binary data in the right order can be tedious and very error-prone.• In most cases you’ll need arbitrary data to be read and
written.
• Even worse, your type hierarchy will evolve during development.
• We’ll take a look at two more generic approaches that try to ensure mostly error-free binary serialization.
8 / 58
Initial Situation
C#
9 / 58
public class Address
{
public string PostCode;
public string City;
}
public class OrderItem
{
public string Name;
public float Price;
}
public class Order
{
public OrderItem Item;
public Address ShipTo;
}
Challenge
• In order to be able to automatically serialize and deserialize objects of type Order,• we need to recursively serialize and deserialize objects
of type Address and OrderItem,
• we must be able to read and write field values of primitive types (such as string or float),
• and we must do so in the right order!
10 / 58
Binary Serializationvia Interfaces• All serializable classes implement a new interface
IBinarySerializable.
• Interface enforces methods for reading and writing binary data.• These methods can be called for all types that are
referenced by serialized types.
• Reading and writing data in the correct order relies on a correct implementation of the interface.
11 / 58
InterfaceIBinarySerializable
C#
12 / 58
public interface IBinarySerializable
{
void WriteBinary(BinaryWriter writer);
void ReadBinary(BinaryReader reader);
}
Interface Implementations
C#
13 / 58
public class Address : IBinarySerializable
{
public string PostCode;
public string City;
public void WriteBinary(BinaryWriter writer)
{
writer.Write(this.PostCode);
writer.Write(this.City);
}
public void ReadBinary(BinaryReader reader)
{
this.PostCode = reader.ReadString();
this.City = reader.ReadString();
}
}
Interface Implementations
C#
14 / 58
public class OrderItem : IBinarySerializable
{
public string Name;
public float Price;
public void WriteBinary(BinaryWriter writer)
{
writer.Write(this.Name);
writer.Write(this.Price);
}
public void ReadBinary(BinaryReader reader)
{
this.Name = reader.ReadString();
this.Price = reader.ReadSingle();
}
}
Interface Implementations
C#
15 / 58
public class Order : IBinarySerializable{
public OrderItem Item;public Address ShipTo;
public void WriteBinary(BinaryWriter writer){
this.Item.WriteBinary(writer);this.ShipTo.WriteBinary(writer);
}
public void ReadBinary(BinaryReader reader){
this.Item = new OrderItem();this.Item.ReadBinary(reader);
this.ShipTo = new Address();this.ShipTo.ReadBinary(reader);
}}
Writing Binary Data
C#
16 / 58
// Collect information on the file to create.
FileInfo fileInfo = new FileInfo("newFile.dat");
// Create new file.
FileStream fileStream = fileInfo.Create();
// Create new binary writer.
BinaryWriter binaryWriter = new BinaryWriter(fileStream);
// Write data.
order.WriteBinary(binaryWriter);
// Close file stream and release all resources.
binaryWriter.Close();
Reading Binary Data
C#
17 / 58
// Collect information on the file to read from.
FileInfo fileInfo = new FileInfo("newFile.dat");
// Open file for reading.
FileStream fileStream = fileInfo.OpenRead();
// Create new binary reader.
BinaryReader binaryReader = new BinaryReader(fileStream);
// Read data.
Order order = new Order();
order.ReadBinary(binaryReader);
// Close file stream and release all resources.
binaryReader.Close();
Binary Serializationvia Interfaces - Evaluation• Delegating the task of serialization to serialized
classes increases code readability and makes debugging easier• However, every time a new type is introduced, you need
to implement the interface again.
• Reading and writing data in the correct order relies on a correct implementation of the interface.
• Strictly spoken, this approach violates the principle of separation of concerns.
18 / 58
Binary Serializationvia Reflection• We create a new serialization class called BinarySerializer.
• Similar to XmlSerializer, this class provides Serialize and Deserialize methods that reflect the fields of a given type.
19 / 58
Class BinarySerializer
C#
20 / 58
public void Serialize(BinaryWriter writer, object obj){
if (obj == null){
return;}
// Reflect object fields.Type type = obj.GetType();FieldInfo[] fields = type.GetFields
(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
// ...
Class BinarySerializer
C#
21 / 58
// ...
foreach (FieldInfo field in fields){
// Check how the field value has to be serialized.object fieldValue = field.GetValue(obj);
if (field.FieldType == typeof(string)){
writer.Write((string)fieldValue);}else if (field.FieldType == typeof(float)){
writer.Write((float)fieldValue);}else if (field.FieldType == typeof(int)){
writer.Write((int)fieldValue);}
// ...}
}
Class BinarySerializer
C#
22 / 58
foreach (FieldInfo field in fields){
// ...
else if (!field.FieldType.IsPrimitive){
// Recursively serialize referenced types.this.Serialize(writer, fieldValue);
}else{
throw new ArgumentException(string.Format("Unsupported type for binary serialization: {0}.Cannot serialize fields of type {1}.", type, field.FieldType), "obj");
}}
}
Class BinarySerializer
C#
23 / 58
public object Deserialize(BinaryReader reader, Type type){
// Create object instance.object obj = Activator.CreateInstance(type);
// Reflect object fields.FieldInfo[] fields = type.GetFields
(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
// ...
Class BinarySerializer
C#
24 / 58
// ...
foreach (FieldInfo field in fields){
object fieldValue;
// Check how the field value has to be deserialized.if (field.FieldType == typeof(string)){
fieldValue = reader.ReadString();}else if (field.FieldType == typeof(float)){
fieldValue = reader.ReadSingle();}else if (field.FieldType == typeof(int)){
fieldValue = reader.ReadInt32();}
// ...}
}
Class BinarySerializer
C#
25 / 58
foreach (FieldInfo field in fields){
// ...
else if (!field.FieldType.IsPrimitive){
// Recursively deserialize referenced types.fieldValue = this.Deserialize(reader, field.FieldType);
}else{
throw new ArgumentException(string.Format("Unsupported type for binary deserialization: {0}.Cannot deserialize fields of type {1}.", type, field.FieldType), "type");
}
// Set field value.field.SetValue(obj, fieldValue);
}
return obj;}
Writing Binary Data
C#
26 / 58
// Collect information on the file to create.
FileInfo fileInfo = new FileInfo("newFile.dat");
// Create new file.
FileStream fileStream = fileInfo.Create();
// Create new binary writer.
BinaryWriter binaryWriter = new BinaryWriter(fileStream);
// Write data.
BinarySerializer serializer = new BinarySerializer();
serializer.Serialize(binaryWriter, order);
// Close file stream and release all resources.
binaryWriter.Close();
Reading Binary Data
C#
27 / 58
// Collect information on the file to read from.
FileInfo fileInfo = new FileInfo("newFile.dat");
// Open file for reading.
FileStream fileStream = fileInfo.OpenRead();
// Create new binary reader.
BinaryReader binaryReader = new BinaryReader(fileStream);
// Read data.
BinarySerializer serializer = new BinarySerializer();
Order order = (Order)serializer.Deserialize(binaryReader, typeof(Order));
// Close file stream and release all resources.
binaryReader.Close();
Binary Serializationvia Reflection - Evaluation• Newly created types don’t need to implement any
interfaces.
• Special cases (enums, nullable types, generics) need to be considered only once.
• Serialization code is limited to very few lines, which in turn can be found in a single class.• However, serialization via reflection is significantly
slower than via interfaces.
• Reading and writing data in the correct order depends on the order the fields are declared in serialized types!
28 / 58
Hint
Never stop improving your error handling while developing!
(e.g. missing default constructor, unexpected enum value, etc.)
30 / 78
Background Workers
• Time-consuming operations like downloads and database transactions can cause your UI to seem as though it has stopped responding while they are running.
• BackgroundWorker class allows you to run an operation on a separate, dedicated thread.
31 / 78
Background Workers 101
1. Create new BackgroundWorker object.
2. Add event handlers.1. Perform your time-consuming operation in DoWork.
2. Set WorkerReportsProgress to true and receive notifications of progress updates in ProgressChanged.
3. Receive a notification when the operation is completed in RunWorkerCompleted.
3. Call RunWorkerAsync on the worker object.
32 / 78
Gotcha!
Don’t to manipulate any UI objects in your DoWork event handler!
33 / 78
Background Worker and UI
34 / 78
Reporting Progress
• Calling ReportProgress on the background worker object causes the ProgressChanged event handler to be called in the UI thread.
• In that event handler, the reported progress is available through the property ProgressChangedEventArgs.ProgressPercentage.
35 / 58
Passing Parameters
• If your background operation requires a parameter, call RunWorkerAsync with your parameter.
• Inside the DoWork event handler, you can extract the parameter from the DoWorkEventArgs.Argument property.
36 / 58
Returning Results
• If your background operation needs to return a result, set the DoWorkEventArgs.Result property in your DoWork event handler after your operation is finished.
• Inside the RunWorkerCompleted event handler of your UI thread, you can access the result from the RunWorkerCompletedEventArgs.Result property.
37 / 58
Assignment #6
1. Status Bar
Add a StatusBar with a TextBlock and a ProgressBarto your MainWindow.
38 / 58
Assignment #6
2. Worker Threads
1. Modify your application and make creating new mapshappen in a background worker thread.
2. Your status text and progress bar should reflect theprogress of the map creation.
39 / 58
References
• MSDN. BackgroundWorker Class. http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker%28v=vs.110%29.aspx, May 2015.
40 / 58
Thank you for your attention!
Contact
Blog
http://www.npruehs.de
@npruehs
Github
https://github.com/npruehs
41 / 58