If you ever got to tango with performance counters in C# (+1, they can be very useful) you might have noticed that the current API leaves much to be desired. Things can get even more cluttered when you have a bunch of them. You have to store the name, help string, and type for each one so you can feed it to the API later on. You'll also need to store an instance for each counter so you can have its value updated. The other day a colleague of mine mentioned that the task of adding a new counter becomes a chore.
The good news is, you can make your life easier.
The mechanism that will assist you in doing so consists of the following key elements:
- An enum representing the performance counters we're going to have. Each counter will have a corresponding member in this enum, future counters will be added here as well.
- A set of attributes to decorate the above mentioned enum and provide a declarative way of conveying counter metadata.
- Some extension methods to alleviate metadata retrieval and make things cleaner.
- A class tying it all up in a neat API.
The attributes look like so:
Which makes your counters enum to look like so:
Now we add some extension methods to easily extract relevant information from the counters enum:
And, tie it all up in a neat API:
The grand finale looks like so:
This mechanism has some nice benefits:
- Adding a new counter basically boils down to adding a new member to the enum (and decorating it accordingly). Since all enum members are iterated over automatically, it will be added to your category with no further effort on your side.
- All information pertaining to a particular counter is grouped together and is thus easily conveyed to the reader (no more constants representing counter names scattered all around, no more "wait, I can see the name but what was this counter's type again?").
- With further work it can be leveraged into an infrastructural module applicable to any client system (which is not the case at the moment, since the enum is hard-coded with particular client's counters, making it unusable for other clients out of the box) - see the epilogue for more details.
This mechanism can be further improved by eliminating the coupling to a particular enum (i.e., to a particular client), and basically turned into a generic infrastructure component. The keen reader may have noticed that the extension methods are defined on the counters enum, which is client specific - not a fun fact at all.
The direction I had in mind was turning the enum into an interface with get properties decorated pretty much same manner as the current enum. Using reflection we could have a mapping between the property names and actual counter instances. Then we could use castel's DictionaryAdapter to implement that interface and have an object with actual properties which use an underlying mapping (string => counter) for the get / set functionality. The extension methods would be defined on a marker interface (i.e., an empty interface), so that any interfaces down the hierarchy could get them for free. This should allow for any client to define an interface deriving from this marker interface, define its own properties, and get all the perks we've mentioned right out of the box.
So how bout them apples?