Complex Assertions using C# 3.0

Recently I attempted to implement a declarative predicate checking system to allow design by contract (DBC) within C# 3.0. I was not successful due to a limitation in the kind of parameters one can pass to an Attribute constructor in .NET (no lambdas). I thought I’d just follow that up with a simpler model based on extension methods.

public static class Predicates
{
    public static void Assert<T>(this T obj, Func<T, bool> pred)
    {
        if (!pred(obj))
            throw new ApplicationException();
    }
}

This simple extension method can be attached to any object allowing Ensures and Requires like this.

int MyIntProp{get;set;}
public void MyMethod()
{
    this.Assert(x => x.MyIntProp < 10);
    MyIntProp += 10;
    this.Assert(x => x.MyIntProp >= 10);
}

This is a nice clear implementation that is good for validation. But I think that I can extend it further by exploiting serialization of snapshots within a scope to allow before/after analysis within the scope. Here’s what I want to be able to write:

public void MyBetterMethod()
{
    this.Require(x => x.MyIntProp < 10);
    MyIntProp += 10;
    this.Ensure(x => x.MyIntProp == x.before().MyIntProp + 10);
}

Well, my recent writings about the Ambient Context pattern might give you a clue about how I would manage the scope. The first thing I need to be able to do is store a snapshot of the object before it gets tested by the Require. I chose an IDisposable object so that I can clean up after myself without the danger of having the serialized guts of objects lying around everywhere.

public class PredicateScope : IDisposable
{
    [ThreadStatic]
    public static Stack<PredicateScope> Scopes = new Stack<PredicateScope>();
    internal readonly Dictionary<object, string> Snapshots = new Dictionary<object, string>();
    internal readonly Dictionary<object, object> DeserializedSnapshots = new Dictionary<object, object>();

    public PredicateScope(params object[] objects)
    {
        foreach (object obj in objects)
        {
            Snapshots.Add(obj, CreateSnapShot(obj));
        }
        Scopes.Push(this);
    }
    static string CreateSnapShot(object obj)
    {
        XmlSerializer serializer = new XmlSerializer(obj.GetType());
        StringWriter sr = new StringWriter();
        serializer.Serialize(sr, obj);
        return sr.ToString();

    }

    public void Dispose()
    {
        Snapshots.Clear();
        Scopes.Pop();
    }
}

You just pass the scope object whatever objects you intend to test later on. It takes snapshots of the objects and stores them away for later reference. It also maintains a stack, so it can be nested. Strictly speaking this is unnecessary, but I figure it might come in handy later on.

My Assertion methods are pretty much the same, but they’re now augmented by a “before” extension method that will get a snapshot keyed to the object it’s extending, and return that instead.

public static class Predicates
{
    public static void Require<T>(this T obj, Func<T, bool> pred)
    {
        if (!pred(obj))
            throw new ApplicationException();
    }

    public static void Ensure<T>(this T obj, Func<T, bool> pred)
    {
        if (!pred(obj))
            throw new ApplicationException();
    }

    public static T before<T>(this T obj) where T : class
    {
        if (obj == null)
            throw new ArgumentNullException("obj cannot be null");

        PredicateScope ctx = PredicateScope.Scopes.Peek();
        if (ctx == null) return default(T);

        if (ctx.DeserializedSnapshots.ContainsKey(obj))
            return ctx.DeserializedSnapshots[obj] as T;
        string serializedObject = ctx.Snapshots[obj];
        XmlSerializer ser = new XmlSerializer(typeof(T));
        XmlReader reader = XmlReader.Create(new StringReader(serializedObject));
        object result = ser.Deserialize(reader);
        ctx.DeserializedSnapshots[obj] = result;
        return result as T;

    }
}

The before method gets the snapshot out of the scope, and returns that. You can then use it in your assertions in exactly the same way as the original object.

[TestFixture, DataContract]
public class MyClass
{
    [DataMember]
    public int MyInt { get; set; }
    [Test]
    public void MyMethod()
    {
        using (new PredicateScope(this))
        {
            this.Require(x => x.MyInt < 10);
            MyInt += 10;
            this.Ensure(x => MyInt == x.before().MyInt + 10);
        }
    }
}

Obviously, for production use you’d have to ensure this stuff didn’t get run by using ConditionalAttribute. It would affect performance. But for debugging it can be a godsend.

About these ads

5 comments

  1. This is a pretty cool way to wedge DBC into C# with minimal boilerplate. But I have some reservations.

    I have to admit I’m a little uncomfortable with the way you’ve implemented preservation of the “before” state of the object….

    I realize that in order to support the “before” functionality, you need to be able to copy the object’s properties… But if they are complex objects, it seems like storing off a copy of every property regardless whether they are actually referenced in the constraints could sometimes have prohibitive performance impacts, even for debug/test mode.

    Then there’s the text overhead of XML in memory. Though without attributes and all that jazz, it might not be so bad.

    I’m also not sure I see the value in keeping a static stack of scopes. This might be useful with recursion I suppose. But beyond that, it seems like it would imply a very tight coupling between different methods.

    I know the contract should be thought of as being one layer above the algorithm/code itself. But if constraints in a method are dependent on what’s going on in the calling method, that really implies to me that the methods are way too tightly coupled.

  2. Hi Waterbreath,

    Thanks – see the next post i made, where I introduce Lambda Expressions to make debugging more intuitive.

    I took the technique for storing serialized snapshots from Juval Lowy’s Transactional, where he used serialisation to store the state in case of rollback. It will create an overhead, but as you will have noticed, the parts that take the snapshots are marked with Conditional(“DEBUG”), so that they will not encumber the system in production. The point of this is to augment debugging facilities. After all, finding the constraint violation in production using DBC is not going to help (tho it would be nice) since the program is still broken.

    I see your point about the nesting of scopes – it was kind of unnecessary really. A simple static class would have sufficed.

    Cheers

    Andrew

Comments are closed.