Home > .net 2.0, .net 3.0, .net 3.5, c#, LINQ, Visual Studio 2008 > Filtering an Enum by Attribute

Filtering an Enum by Attribute

July 9, 2009

I had a curve ball thrown at me this morning—changing requirements.  It happens and was easily solved by a couple of custom attributes and a helper method.

UPDATE: I’ve updated the code (and explaination) for FilterEnumWithAttributeOf below to tidy it up a bit.

In our current project, there is an enum of standard, static “periods” (times of days students are in school).  Easy enough.

BeforeSchool = 0,
FirstPeriod = 1,
SecondPeriod = 2,
etc.

But what happens if we want to “query” our list down a bit… say a certain group only wanted a subset of the “periods”.

I could create an entirely different Enum — Group1Period and Group2Period.

But then handling things in FluentNHibernate’s automapping would get freaked out with the Period property.

So, what about a custom attribute?

  1. I can assign multiple custom attributes to the same Enum field so I can be in Group1 and Group2 at the same time.
  2. I can keep the same Enum “Period” for my ORM layer.
  3. Now how do I query it down…?

Here’s an abstracted example of how the enum looks right now:

public enum Period

{

    [Elementary][Secondary]

    [Description(“Before School”)]

    BeforeSchool = 0,

 

    [Elementary]

    Homeroom = 12,

 

    [Secondary]

    [Description(“1st”)]

    First = 1,

}

Elementary and Secondary (our two groups, in this case) are “logicless” attributes (I’m just looking at them as flags, not passing/storing information).

[AttributeUsage(AttributeTargets.Field)]

public class ElementaryAttribute : Attribute

{

}

 

[AttributeUsage(AttributeTargets.Field)]

public class SecondaryAttribute : Attribute

{

}

Now, to filter out those pesky periods based on the attributes.

Update:

Old Code!

public IEnumerable<TEnum> FilterEnumWithAttributeOf<TEnum, TAttribute>()

{

    foreach (var field in

        typeof (TEnum).GetFields(BindingFlags.GetField |

                                 BindingFlags.Public |

                                 BindingFlags.Static))

    {

        foreach (var attribute in

            field.GetCustomAttributes(typeof (TAttribute), false))

        {

            yield return (TEnum) field.GetValue(null);

        }

    }

}

New Code!

public static IEnumerable<TEnum> FilterEnumWithAttributeOf<TEnum, TAttribute>()

    where TEnum : struct

    where TAttribute : class

{

    foreach (var field in

        typeof(TEnum).GetFields(BindingFlags.GetField |

                                 BindingFlags.Public |

                                 BindingFlags.Static))

    {

 

        if (field.GetCustomAttributes(typeof(TAttribute), false).Length > 0)

            yield return (TEnum)field.GetValue(null);

    }

}

Why new code?

Well, after looking over the code, I don’t need to iterate through each attribute, simply see if the field contains it (Length > 0).  If it does, then return it.  That cuts a loop out of our code and performs the same function.  I also added two generic constraints.  You can’t constrain by Enum, but struct works well.

I’m passing in two generics in this case—TEnum, which is the type of the of the Enum and TAttribute.. the type of the attribute.  Yeah, I realize that my creativity of naming is pretty low.  Work with me here, alright?😉

Past that, the loops are pretty easy.

  1. Loop through each field of the enumeration.  Return the field (GetField) and be sure to check Public and Static fields.
  2. Loop through each custom attribute on each field (returned by GetField) and only return the fields that match the type of our attribute.  I pass along the false parameter (do not inherit) because I’m not interested in inherited attributes. You could leave this as true. YMMV.
  3. If the field’s attribute’s contains our type, yield out the actual Enum value (a string of the field isn’t as useful).

Now, for using it…

var enums = FilterEnumWithAttributeOf<Period, ElementaryAttribute>();

 

foreach (var period in enums)

{

    Console.WriteLine(“{0}, {1}”.AsFormatFor(period, (int)period));

}

Easy enough.  ElementaryAttribute returns:

BeforeSchool, 0
Homeroom, 12
AfterSchool, 10
etc..

Running the same code, but asking for SecondaryAttribute returns:

BeforeSchool, 0
First, 1
Second, 2
etc..

Sweet.

Tags: , ,
%d bloggers like this: