<History>
<Version>
<VersionInfo Number="1" Timestamp="2010-02-11 13:38:04" />
<Commit>
<User>MyComputerName\MyUsername</User>
<Name>Brian</Name>
<Notes>Initial commit.</Notes>
</Commit>
<Configuration>
<Something>Initial Value</Something>
</Configuration>
</Version>
<Version>
<VersionInfo Number="2" Timestamp="2010-02-11 14:16:52" />
<Commit>
<User>MyComputerName\MyUsername</User>
<Name>Brian</Name>
<Notes>Changed something.</Notes>
</Commit>
<Configuration>
<Something>Changed Value</Something>
</Configuration>
</Version>
</History>
The original version of these classes look as you would expect (versions were stored in a generic list). The client would serialize a Commit object and a changed Configuration object and send them to the server. The server would create a Version object, give it a VersionInfo object with a Number and a Timestamp, add it to the History, then save it to disk. The client would then update its history.
This would have been fine and dandy if it weren't for my paranoia. I feared that some future developer would modify the history after it was committed. This completely violates the idea of a history. So I set about devising a way to prevent that. What I needed were mostly-immutable objects - objects, whose properties could be set only once.
What I came up with was a pair of generic classes: Immutable<T> and ImmutableList<T>.
public class Immutable<T>
{
private bool isSet;
private T value;
public T Value
{
get
{
return this.value;
}
set
{
if (isSet)
{
throw new InvalidOperationException();
}
this.value = value;
isSet = true;
}
}
}
public class ImmutableList<T> : IList<T>
{
private readonly List<T> list = new List<T>();
public void Add(T item)
{
this.list.Add(item);
}
public void Clear()
{
throw new InvalidOperationException();
}
public bool Remove(T item)
{
throw new InvalidOperationException();
}
public void Insert(int index, T item)
{
this.list.Insert(index, item);
}
public void RemoveAt(int index)
{
throw new InvalidOperationException();
}
public T this[int index]
{
get
{
return this.list[index];
}
set
{
throw new InvalidOperationException();
}
}
<snip>
}
Immutable<T> only lets you set its value once, and ImmutableList<T> only lets you add or insert items - never clearing, removing, or replacing. If you attempt to violate these rules, a big, nasty InvalidOperationException is thrown.
public class History
{
private readonly Immutable<ImmutableList<Version>> versions = new Immutable<ImmutableList<Version>>();
public ImmutableList<Version> Versions
{
get
{
return versions.Value;
}
set
{
versions.Value = value;
}
}
}
As you can see, History can only have its Versions property set once, which is all you need when XML deserializing. You'll also only be able to add to a Version - never remove or replace.
public class Version
{
private readonly Immutable<VersionInfo> versionInfo = new Immutable<VersionInfo>();
private readonly Immutable<Commit> commit = new Immutable<Commit>();
private readonly Immutable<Config> configuration = new Immutable<Config>();
public VersionInfo VersionInfo
{
get
{
return this.versionInfo.Value;
}
set
{
this.versionInfo.Value = value;
}
}
public Commit Commit
{
get
{
return this.commit.Value;
}
set
{
this.commit.Value = value;
}
}
public Configuration Configuration
{
get
{
return this.configuration.Value;
}
set
{
this.configuration.Value = value;
}
}
}
One downside to this is I can't use my beloved automatic properties for dumb data object like these. But, I suppose they're not quite as dumb now, are they?
uhhhh. My head hurts can i have an Advil now? Seriously i'm glad there are guys like you that do this for a living because if I ever had to start coding like that all day I would blow my brains out. Good luck on the project though
ReplyDeleteGood stuff. Would be nice if the Freezable* class was in the mainline instead of being stuffed up in System.Windows for popsicle immutability.
ReplyDelete* http://msdn.microsoft.com/en-us/library/system.windows.freezable.aspx
@Brandon: I think the same thing when you talk about IT stuff.
ReplyDelete@Thad: Freezable seems to be too tightly bound to WPF/Silverlight. Perhaps I'll modify my classes to behave a little more like Freezable. (It never occurred to me to look at Freezable when I wrote this)
Like the blog Brian. Thanks for the kata template. Gave you props and shared it on facebook.
ReplyDeleteSlingo Games - Sign up now and play for real money right from your
ReplyDeleteSlingo is one of 바카라 사이트 the filmfileeurope.com most popular and popular slots septcasino on the https://access777.com/ market today. Enjoy ventureberg.com/ classic, traditional slots from the comfort of your home with our