How to separate data from logic when serializing types - grid tile map example
I have been experimenting and found this solution to be the best:

[Serializable]
public class Tile
{
    [field: NonSerialized]
    public event EventHandler&lt;TileTypeChangedEventArgs&gt; 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.