July 11, 2009

.NET Extension Methods and Enumerations

Much of my code play recently has been with .NET/Mono in C#. One of the cooler aspects of 3.x has been extension methods by which I can add methods to established types. For instance if I'm doing a bunch of range checking on integers the traditional way would be:

if ((3 <= i) && (i <= 9))
{
...
}

With extension methods I can create an extension that allows me to do:

if (i.Within(3,9))
{
...
}


I do this by creating a static class like below:

static class MyExtensionMethods
{
public static bool Within(this int me, int a, int b)
{
// this is faster if we require a to be the
// lower bound and b to be
// the upper bound, but to be complete...
return (Math.Min(a,b) <= me) &&
(me <= Math.Max(a,b));
}
}


The tricky part is applying this to certain types such as enumerations. One thing I do a lot with enums is testing if an or'd set contains one given enum value. For instance to determine if the left mouse button is pressed I would use:

if ((Control.MouseButtons & MouseButtons.Left) == 
MouseButtons.Left)
{
// Handle the left button down
}


So I can create an extension method for MouseButtons enum:

public static bool Contains(this MouseButtons me, 
MouseButtons other)
{
return (me & other) == other;
}


That's handy but I don't want to create this for every enum I touch. So now I want to create an extension method that extends any enum. The first try is:

public static bool Contains(this Enum me, Enum other)
{
return (me & other) == other;
}

This works but I can do Control.MouseButtons.Contains(MenuMerge.Add) which might work but isn't really valid. So let's try to change it:

public static bool Contains(this T me, T other)
where T : enum
{
return (me & other) == other;
}

This is how it should work, however MS in all their wisdom has decided enums cannot be set as a where statement on methods or classes. So we have to cheat and introduce some possible problems that wouldn't show up until run time. We can use a struct as a where clause and enums are structs, but then we have to test that the type T is actually an enum. This leads to the final form:

public static bool Contains(this T me, T other)
where T : struct
{
if (!typeof(Enum).IsAssignableFrom(typeof(T)))
thrown new InvalidCastException();

return (me & other) == other;
}

This works, but the compiler won't throw an exception on using the Contains method on regular structs. You will get a run time exception but that can be messy so use at your own discretion.

No comments:

Post a Comment