Chapter 06 of the lecture Tool Development taught at SAE Institute Hamburg.
Introduction to different approaches to binary serialization, as well as to worker threads in WPF.
3. Objectives
• To learn how to properly read and write binary files
• To understand how to use worker threads in order
to create reactive UIs
3 / 58
4. 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
4 / 58
5. Writing Binary Data To Files
C#
5 / 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();
6. Reading From Binary Files
C#
6 / 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();
7. 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.
7 / 58
8. Initial Situation
C#
8 / 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;
}
9. 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!
9 / 58
10. Binary Serialization
via 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.
10 / 58
12. Interface Implementations
C#
12 / 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();
}
}
13. Interface Implementations
C#
13 / 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();
}
}
14. Interface Implementations
C#
14 / 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);
}
}
15. Writing Binary Data
C#
15 / 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();
16. Reading Binary Data
C#
16 / 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();
17. Binary Serialization
via 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.
17 / 58
18. Binary Serialization
via 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.
18 / 58
19. Class BinarySerializer
C#
19 / 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);
// ...
20. Class BinarySerializer
C#
20 / 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);
}
// ...
}
}
21. Class BinarySerializer
C#
21 / 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");
}
}
}
23. Class BinarySerializer
C#
23 / 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();
}
// ...
}
}
24. Class BinarySerializer
C#
24 / 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;
}
25. Writing Binary Data
C#
25 / 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();
26. Reading Binary Data
C#
26 / 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();
27. Binary Serialization
via 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!
27 / 58
28. Hint
Never stop improving your error
handling while developing!
(e.g. missing default constructor,
unexpected enum value, etc.)
29 / 78
29. 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.
30 / 78
30. 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.
31 / 78
33. 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.
34 / 58
34. 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.
35 / 58
35. 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.
36 / 58
36. Assignment #6
1. Status Bar
Add a StatusBar with a TextBlock and a ProgressBar
to your MainWindow.
37 / 58
37. Assignment #6
2. Worker Threads
1. Modify your application and make creating new maps
happen in a background worker thread.
2. Your status text and progress bar should reflect the
progress of the map creation.
38 / 58
40. 5 Minute Review Session
• What are two possible approaches for binary
serialization?
• What are the advantages and disadvantages of binary
serialization via interfaces?
• What are the advantages and disadvantages of binary
serialization via reflection?
• Why and when should you use a Background Worker in
your UI?
• How do you work with Background Workers?
• How do you pass data to and from Background
Workers?
41 / 58