Headers - attaching arbitrary properties to objects

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);
}
A picture of me

Welcome, my name is Paul Stovell. I live in Brisbane and work on Octopus Deploy, an automated deployment tool for .NET applications.

Prior to founding Octopus Deploy, I worked for an investment bank in London building WPF applications, and before that I worked for Readify, an Australian .NET consulting firm. I also worked on a number of open source projects and was an active user group presenter. I was a Microsoft MVP for WPF from 2006 to 2013.

14 Aug 2011

I'd never come across ConditionalWeakTable before. Seems like a neat way to use it. Thanks for the info.

HeaderCollection is another class I've never come across. From how you've used it, I assume it's just an IDictionary< string, string>?

You could also make the value an object rather than string, to allow you to do message.SetHeader("CorrelationId", 45).

14 Aug 2011

Jason, yes, HeaderCollection is just a custom Dictionary<string, string> wrapper. I started doing that after seeing the ASP.NET MVC source code and how they tend to use custom collection types to be more specific about semantics (for example, in a HeaderCollection, key lookups are not case specific).

15 Aug 2011

Hi Paul,

Interesting. Not unlike the old Extension Providers in .NET, however I do wonder whether this improves the grok-ability of the code or not.

15 Aug 2011

Hi Mitch, you're right. The trick with ExtensionProviders is you needed somewhere to store the extended state - this usually went into a dictionary. That would be a problem for the GC if the extension provider lived longer than the object being extended (thankfully not usually the case in WinForms). ConditionalWeakTable would be a good backing store for those extender providers.