Headers - attaching arbitrary properties to objects

This is an old post and doesn't necessarily reflect my current thinking on a topic, and some links or images may not work. The text is preserved here for posterity.

On a project recently I had a marker interface like this:

public interface IMessage
{
}

I wanted the ability to attach arbitrary headers to a message - for example:

message.SetHeader("Expires", "2011-09-03 15:30:00");
message.SetHeader("CorrelationId", 45);

I didn't want SetHeader to be a method on the interface, so I created an extension method. But how can we store the arbitrary header values against the object instance?

My solution was to use ConditionalWeakTable. This allows properties to be stored against objects without preventing the object from being garbage collected.

The implementation looked something like this:

public static class MessageExtensions
{
    private static readonly ConditionalWeakTable<IMessage, HeaderCollection> headerMap = new ConditionalWeakTable<IMessage, HeaderCollection>();

    public static void SetHeader(this IMessage message, string header, string value)
    {
        var headers = GetHeaders(message);
        headers[header] = value;
    }

    public static string GetHeader(this IMessage message, string header)
    {
        var headers = message.GetHeaders();
        string value;
        headers.TryGetValue(header, out value);
        return value;
    }

    public static HeaderCollection GetHeaders(this IMessage message)
    {
        return headerMap.GetValue(message, x => new HeaderCollection());
    }
} 

I put the extension methods in the global namespace, so that they are available on any IMessage without the coder needing to include the namespace.

Here is a unit test to demonstrate the garbage collection continuing to work:

[TestMethod]
public void HeadersDoNotPreventGarbageCollection()
{
    WeakReference reference = null;

    new Action(() =>
    {
        var message1 = new MessageA();
        message1.SetHeader("Test", "Foo");

        reference = new WeakReference(message1, true);
        Assert.IsNotNull(reference.Target);
    })();

    GC.Collect();
    GC.WaitForPendingFinalizers();

    Assert.IsNull(reference.Target);
}