I have a binary file format I cannot change, and in C# I need to deserialize class instances from it again.
The file contains several byte blocks which represent object instances of a base class. However, one byte in the block specifies which subclass the object once was.
For example, a byte block looks like the following:
Collectible
)Coin
or ItemBox
)Coin
(e.g. the coins value) or the ItemBox
(e.g. which item it has and how fast it regenerates)I currently coded a constructor for the Collectible
class accepting a BinaryReader to read from. However, after I read the general settings and know which sub type follows, I can't downcast the Collectible
class to Coin
or ItemBox
depending on the type when I'm already in the constructor.
public Collectible(BinaryReader reader)
{
// General collectible stuff, position, synced anim...
X = reader.ReadInt32();
Y = reader.ReadInt32();
MusicStartCode = reader.ReadInt32();
MusicBpm = reader.ReadInt32();
// ...
// Tricky part: sub-type specific information follows
CollectibleType type = (CollectibleType)reader.ReadByte();
switch (type)
{
case CollectibleType.Coin:
// What to do now?
break;
case CollectibleType.ItemBox:
// I ran out of ideas
break;
}
}
How do I solve such a problem elegantly and deserialize the binary format back into object instances? I thought of some kind of seeking towards the byte determining the sub type and then create the specific sub class instances. But where would I do that, is there a typical design pattern for this problem?
On a sidenote I think that this format was written by a C++ program originally which just dumped the instances bytes into the file and had no problem casting a byte array pointer back to a class when loading it. But I can't do that in C# or can I (I want to avoid unsafe code)?
How about something along the lines of this:
public class CollectionFactory
{
private readonly BinaryReader _reader;
public CollectionFactory(BinaryReader reader)
{
if (reader == null)
{
throw ArgumentNullException(reader);
}
_reader = reader;
}
public CollectionType CreateInstance()
{
using (_reader)
{
// General collectible stuff, position, synced anim...
var x = _reader.ReadInt32();
var y = _reader.ReadInt32();
var musicStartCode = reader.ReadInt32();
var musicBpm = reader.ReadInt32();
// ...
// Tricky part: sub-type specific information follows
CollectibleType collectableType = (CollectibleType)reader.ReadByte();
if (collectableType is Coin)
{
return new Coin
{
X = x,
Y = y,
MusicStartCode = musicStartCode
// etc..
};
}
if (collectableType is ItemBox)
{
return new ItemBox
{
X = x,
Y = y,
MusicStartCode = musicStartCode
// etc..
};
}
return null;
}
}
}
And then consume it:
var collectionFactory = new CollectionFactory(reader);
var collType = collectionFactory.CreateInstance();
// cast to specific type if needed here.
var itemBox = collType as ItemBox;
if (itembox != null)
{
// Do stuff..
}