Showing posts with label XML Serialization. Show all posts
Showing posts with label XML Serialization. Show all posts

Wednesday, November 10, 2010

XML Serializable Immutable Objects and Lists

In my current project, I'm using a client/server architecture, where, among other things, the client tells the server what its configuration should be. Because multiple clients can change the server's configuration, I found it necessary to maintain the history of all configurations that have been applied to the server. I modeled the workflow after Subversion, where clients have the ability to update and commit (but no branching or merging). In order to keep things simple, I decided to store the history as a flat file - an XML serialization of the classes that represent the history. This file looks something like this:

<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?