I've been experimenting with detecting changes in plain objects in C#. The aim being to have a container-type class for a bunch of data objects that can react when any one of them changes. For fun I wanted to see if all the work could be done in the container class, rather than resort to properties and dirty flags or events on the objects themselves.
What I'm curious about is whether there is a smart, fast and efficient way of doing this. My attempt is below, and it's none of those (the 'CheckStates' method would need to be called every frame for a start!) I've restricted it to only allow one instance per type, which suits my needs.
Note that an object passed in might be as follows:
[Serializable]
public class PlayerInfo
{
public string name = string.Empty;
public int score = 0;
}
Then the container:
public class AppState
{
private class StateData
{
public System.Object instance = null;
public Byte[] currentState = new Byte[0];
public Byte[] previousState = new Byte[0];
}
private Dictionary<Type, StateData> _allStates = new Dictionary<Type, StateData>();
private BinaryFormatter _formatter = new BinaryFormatter();
private MemoryStream _memoryStream = new MemoryStream();
public T GetState<T>() where T : class, new()
{
T state = default(T);
var stateType = typeof(T);
StateData stateData;
if(_allStates.TryGetValue(stateType, out stateData))
{
state = ReadData<T>(stateData);
}
else
{
var newState = CreateData<T>(out state);
_allStates[stateType] = newState;
}
return state;
}
public void CheckStates()
{
foreach(var state in _allStates)
{
if(HasChanged(state.Value))
{
Console.WriteLine(state.Key.ToString() + " has changed");
UpdateState(state.Value);
}
}
}
private StateData CreateData<T>(out T instance) where T : class, new()
{
instance = new T();
var stateData = new StateData();
stateData.instance = instance;
_formatter.Serialize(_memoryStream, instance);
var bytes = _memoryStream.ToArray();
stateData.currentState = bytes;
stateData.previousState = bytes;
return stateData;
}
private T ReadData<T>(StateData data) where T : class, new()
{
return data.currentState as T;
}
private bool HasChanged(StateData data)
{
_memoryStream.Position = 0;
_formatter.Serialize(_memoryStream, data.instance);
var current = _memoryStream.ToArray();
var previous = data.previousState;
if(current.Length != previous.Length)
{
return true;
}
for(int i = 0; i < current.Length; ++i)
{
if(current[i] != previous[i])
{
return true;
}
}
return false;
}
private void UpdateState(StateData data)
{
_memoryStream.Position = 0;
_formatter.Serialize(_memoryStream, data.instance);
data.previousState = _memoryStream.ToArray();
}
}
Alternatives I could think of were:
- use structs instead of serializable classes (being forced to pass by value would mean that any change would have to go through a 'set' method on the container)
- have the AppState's 'GetState' method return an IDisposable wrapper, which on Dispose could trigger a check for changes on that type (only problem is that there's nothing to stop someone from storing a reference to the object and modifying it without the container knowing)
EDIT: should add that it doesn't need to be thread-safe