다음을 통해 공유


Enumerating Enums

In this post i will expose a little solution to make it more easy to read different values stored in a single enum instance. This sample is written in C# 2.0 using iterators and operator overriding.

Let's use a simple enum definition:

 [Flags]
public enum Day { None = 0, Monday = 1, Tuesday = 2, Wednesday = 4, Thursday = 8, Friday = 16, Saturday = 32, Sunday = 64 };

.Net allows to manually define values of each element of an enum definition. Playing with bit level representation, you can store a set of values in a single instance. This technic is widely used in win32 APIs (ex: windows styles) but also in the .Net Framework. It brings the smallest memory structure (bits) and the fastest operations ever (binary or, and, etc) to people who do not fear binary.

Just a simple reminder:

  • Defining a set of values: Day weekend = Day.Saturday | Day.Sunday;
  • Testing a value inside a set: if ((weekend & Day.Saturday) > 0) ...

The idea is to provide an enumeration on the different values of the set. The Enum class provides static methods to pass from Enums to integers. This is obligatory because a simple cast is not allowed from T.
Let's try to implement this in a simple method.

 private IEnumerable<T> GetEnumeration<T>(T values) 
{
    if (!typeof(T).IsSubclassOf(typeof(Enum)))
        throw new Exception("Must be an Enum");

    int[] allValues = (int[])Enum.GetValues(typeof(T));

    int intValues = Convert.ToInt32(values);
    foreach (int i in allValues)
    {
        if ((i & intValues) != 0)
            yield return (T)Enum.ToObject(typeof(T), i);
    }
}

Day weekend = Day.Saturday | Day.Sunday;
foreach (Day d in GetEnumeration<Day>(weekend))
    Console.WriteLine(d);

The binary operations are made once for any Enum T. Now we will try to include this implementation in a single structure not to have to keep GetEnumeration<T> available in our context. Infortunately, it is not possible to inherit from Enums. So we will use the Nullable pattern to add our functionnalities to a new generic structure.

 public struct MyEnum<T> : IEnumerable<T>
{
    public MyEnum(T value)
    {
        _value = value;
    }
    private T _value;

    #region IEnumerable<T> Members

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        if (!typeof(T).IsSubclassOf(typeof(Enum)))
            throw new Exception("Must be an Enum");

        int[] allValues = (int[])Enum.GetValues(typeof(T));

        int intValues = Convert.ToInt32(_value);
        foreach (int i in allValues)
        {
            if ((i & intValues) != 0)
                yield return (T)Enum.ToObject(typeof(T), i);
        }
    }

    public override string ToString()
    {
        return _value.ToString();
    }
}

MyEnum<Day> weekend = new MyEnum<Day>(Day.Saturday | Day.Sunday);
foreach (Day d in weekend)
    Console.WriteLine(d);

Like when using nullables, we accept to use another type instead of T. In this first step, we have a simple access to the enumetation: foreach (Day d in weekend). The good news with such a structure is that the memory size does not increase because a structure containing only on field of type T, has the size of T ! Now overriding the cast operator, we will make MyEnum<T> behave like T. Adding cast from T to MyEnum<T> permits not to call MyEnum<T>'s constructor anymore. Adding cast from MyEnum<T> to T permits to make MyEnum<T>'s instances compatible with any code dealing with T.

 public struct MyEnum<T> : IEnumerable<T>
{

    ...

    public static implicit operator T(MyEnum<T> e)
    {
        return e._value;
    }
    public static implicit operator MyEnum<T>(T e)
    {
        return new MyEnum<T>(e);
    }
    public static MyEnum<T> FromEnumerable(IEnumerable<T> e)
    {
        int value = 0;
        foreach (T t in e)
        {
            value |= Convert.ToInt32(t);
        }
        return (T)Enum.ToObject(typeof(T), value);
    }
}

MyEnum<Day> weekend = Day.Saturday | Day.Sunday;
foreach (Day d in weekend)
    Console.WriteLine(d);
Day d2 = weekend;

As you can see, the syntax is really simple and MyEnum<T> only replaces T at the declaration instruction.

All the code we have seen requires C# 2.0. Using C# 3 available in the Linq preview, we have a huge advantage for free: C# 3 extends any IEnumerable<T> with a bunch of new methods. So we can use all set methods with MyEnum<T>. (Intersect, Union, ...). The result values are IEnumerable<T>. That's why we need to add a FromEnumerable method to go back to MyEnum<T>.

 //Only works with C# 3

MyEnum<Day> weekend = Day.Saturday | Day.Sunday;
MyEnum<Day> set1 = Day.Thursday | Day.Wednesday | Day.Saturday;

IEnumerable<Day> result = weekend.Intersect(set1);

Day commonValues = MyEnum<Day>.FromEnumerable(result);

One problem remains. Using enumeration (foreach) and cast overriding is efficient but using "weekend.Intersect(set1)" uses a common algorithm which enumerates both weekend and set1 enumerations. Knowing that a simple "binary and" operator would give the same result, it is really annoying to use such a method even if the syntax is smart. I will explain in my next post how to extend Linq technology to replace high level methods (Intersect, Union) by real binary operations.

See you soon to read: Linq to bits !!!

Mitsu

EnumIterations.zip

Comments

  • Anonymous
    October 07, 2006
    A cool first (well, second if you count the "hello world" post) post from Mitsuru Furuta from Microsoft
  • Anonymous
    October 08, 2006
    Following my previous post , let's see how to extend Linq to bits... To have a good understanding of
  • Anonymous
    October 08, 2006
    Mitsu Furuta, évangéliste chez Microsoft France, vient d'ouvrir un nouveau blog sur la plateforme MSDN:http://blogs.msdn.com/mitsu/Celui-ci...
  • Anonymous
    February 15, 2008
    in c# 2.0 (I know that the same is possible in vb.net, not sure about the syntax though) foreach (Day d in Enum.GetValues(GetType(Day))) diagnostics.writeline (d.toString());