snippetcsharpMinor
How to separate data from logic when serializing types - grid tile map example
Viewed 0 times
typesmapgridserializingexamplelogicseparatehowfromdata
Problem
I am creating a tile based game in Unity and ran into a problem with my architecture of the Map and Tile types. The tile class contains logic like events when the tile type changes. When I tried to save the type to disk those events were being serialized as well, which I don't want (causing several problems in Unity). I basically want to make sure that I only save pure data like x, y position and enum types. Here is my approach:
```
public class Tile
{
public event EventHandler TileTypeChanged;
public const TileType defaultType = TileType.Empty;
public TileType type
{
get { return _type; }
set
{
if (Enum.IsDefined(typeof(TileType), value) == false)
throw new InvalidOperationException("Trying to set undefined tile type.");
if (value != _type)
{
_type = value;
var args = new TileTypeChangedEventArgs(_type);
OnTileTypeChanged(args);
}
}
}
public int x { get { return _x; } }
public int z { get { return _z; } }
TileType _type;
readonly int _x;
readonly int _z;
public Tile(int x, int z, TileType type = Tile.defaultType)
{
_x = x;
_z = z;
this.type = type;
}
protected virtual void OnTileTypeChanged(TileTypeChangedEventArgs e)
{
var handler = TileTypeChanged;
if (handler != null)
handler.Invoke(this, e);
}
}
public class TileTypeChangedEventArgs : EventArgs
{
public TileType type { get; private set; }
public TileTypeChangedEventArgs(TileType type)
{
this.type = type;
}
}
public enum TileType
{
Empty,
Grass,
Water,
Dirt
}
[Serializable]
public sealed class TileData
{
public static readonly int version = 1;
public TileType type
{
get { return _type; }
set { _type = value; }
}
public int x
{
get { return _x; }
se
```
public class Tile
{
public event EventHandler TileTypeChanged;
public const TileType defaultType = TileType.Empty;
public TileType type
{
get { return _type; }
set
{
if (Enum.IsDefined(typeof(TileType), value) == false)
throw new InvalidOperationException("Trying to set undefined tile type.");
if (value != _type)
{
_type = value;
var args = new TileTypeChangedEventArgs(_type);
OnTileTypeChanged(args);
}
}
}
public int x { get { return _x; } }
public int z { get { return _z; } }
TileType _type;
readonly int _x;
readonly int _z;
public Tile(int x, int z, TileType type = Tile.defaultType)
{
_x = x;
_z = z;
this.type = type;
}
protected virtual void OnTileTypeChanged(TileTypeChangedEventArgs e)
{
var handler = TileTypeChanged;
if (handler != null)
handler.Invoke(this, e);
}
}
public class TileTypeChangedEventArgs : EventArgs
{
public TileType type { get; private set; }
public TileTypeChangedEventArgs(TileType type)
{
this.type = type;
}
}
public enum TileType
{
Empty,
Grass,
Water,
Dirt
}
[Serializable]
public sealed class TileData
{
public static readonly int version = 1;
public TileType type
{
get { return _type; }
set { _type = value; }
}
public int x
{
get { return _x; }
se
Solution
I have been experimenting and found this solution to be the best:
The problematic point was the accidental serialization of the event. I didn't know about the [field: NonSerialized] attribute for properties, but this works very well for the BinaryFormatter, XmlSerializer and Json.Net. I also had to add a little bit of ContractResolver logic for the later to deserialize private properties, but I found it much cleaner than having to supply more attributes in my data classes.
Another thing to note is the use of a copy constructor in the Tile class as well as in the Map which holds an array of tiles. When writing data to disk I copy everything before saving to avoid problems when I save on another thread while my users change the current map instance.
I'd still be happy to read any suggestions about how to improve my class design.
[Serializable]
public class Tile
{
[field: NonSerialized]
public event EventHandler TileTypeChanged;
[NonSerialized]
public const TileType defaultType = TileType.Empty;
public TileType type
{
get { return _type; }
set
{
if (Enum.IsDefined(typeof(TileType), value) == false)
throw new InvalidOperationException("Trying to set undefined tile type.");
if (value != _type)
{
_type = value;
var args = new TileTypeChangedEventArgs(_type);
OnTileTypeChanged(args);
}
}
}
public int x { get; private set; }
public int z { get; private set; }
TileType _type;
public Tile() { }
public Tile(int x, int z, TileType type = Tile.defaultType)
{
this.x = x;
this.z = z;
this.type = type;
}
public Tile(Tile original)
{
this.x = original.x;
this.z = original.z;
this.type = original.type;
}
public override string ToString()
{
return string.Format("Tile: {0}|{1} = {2}", x, z, type);
}
protected virtual void OnTileTypeChanged(TileTypeChangedEventArgs e)
{
var handler = TileTypeChanged;
if (handler != null)
handler.Invoke(this, e);
}
}
public class TileTypeChangedEventArgs : EventArgs
{
public TileType type { get; private set; }
public TileTypeChangedEventArgs(TileType type)
{
this.type = type;
}
}
public enum TileType
{
Empty,
Grass,
Water,
Dirt
}The problematic point was the accidental serialization of the event. I didn't know about the [field: NonSerialized] attribute for properties, but this works very well for the BinaryFormatter, XmlSerializer and Json.Net. I also had to add a little bit of ContractResolver logic for the later to deserialize private properties, but I found it much cleaner than having to supply more attributes in my data classes.
Another thing to note is the use of a copy constructor in the Tile class as well as in the Map which holds an array of tiles. When writing data to disk I copy everything before saving to avoid problems when I save on another thread while my users change the current map instance.
I'd still be happy to read any suggestions about how to improve my class design.
Code Snippets
[Serializable]
public class Tile
{
[field: NonSerialized]
public event EventHandler<TileTypeChangedEventArgs> TileTypeChanged;
[NonSerialized]
public const TileType defaultType = TileType.Empty;
public TileType type
{
get { return _type; }
set
{
if (Enum.IsDefined(typeof(TileType), value) == false)
throw new InvalidOperationException("Trying to set undefined tile type.");
if (value != _type)
{
_type = value;
var args = new TileTypeChangedEventArgs(_type);
OnTileTypeChanged(args);
}
}
}
public int x { get; private set; }
public int z { get; private set; }
TileType _type;
public Tile() { }
public Tile(int x, int z, TileType type = Tile.defaultType)
{
this.x = x;
this.z = z;
this.type = type;
}
public Tile(Tile original)
{
this.x = original.x;
this.z = original.z;
this.type = original.type;
}
public override string ToString()
{
return string.Format("Tile: {0}|{1} = {2}", x, z, type);
}
protected virtual void OnTileTypeChanged(TileTypeChangedEventArgs e)
{
var handler = TileTypeChanged;
if (handler != null)
handler.Invoke(this, e);
}
}
public class TileTypeChangedEventArgs : EventArgs
{
public TileType type { get; private set; }
public TileTypeChangedEventArgs(TileType type)
{
this.type = type;
}
}
public enum TileType
{
Empty,
Grass,
Water,
Dirt
}Context
StackExchange Code Review Q#128759, answer score: 2
Revisions (0)
No revisions yet.