In my last post I talked about serializing Enumeration Classes for use with WCF. In this post I’m gonna show you how to add a feature that real enums
have but enumeration classes don’t. Yet!
Enums
can be decorated with the FlagsAttribute
. This allows you to combine single enum
values using the ‘|’ operator. A sample from the System.Reflection
namespace are BindingFlags
.
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
To find out wether a single value is part of such a combination you use the ‘&’ operator.
var flags = (BindingFlags.Public | BindingFlags.Instance) & BindingFlags.Public;
The result will be BindingFlags.Public
.
To implement the same functionality for enumeration classes we need to define a class that implements both operators ‘|’ and ‘&’. This will be the base class for all flags classes
public abstract class Flags<T> : Enumeration where T : Flags<T> { public Flags(int value, string displayName, string name) : base(value, displayName, name) { } public static T operator |(Flags<T> x, Flags<T> y) { return x.Or(y.ToEnumeration()); } public static T operator &(Flags<T> x, Flags<T> y) { var f1 = x.ToArray(); var f2 = y.ToArray(); var f3 = f1.Intersect(f2).ToArray(); if (f3.Length == 0) { return FromValue<T>(0); } if (f3.Length == 1) { return f3[0]; } var or = f3[0].Or(f3[1]); for (int i = 2; i < f3.Length; i++) { or = or.Or(f3[i]); } return or; } protected internal virtual T[] ToArray() { return new[] { this.ToEnumeration() }; } protected abstract T ToEnumeration(); protected abstract T Or(T x); }
To make our lives easier when implementing flags based on enumeration classes we will also use a generic helper class Or<T>
.
public class Or<T> where T : Flags<T> { private readonly List<T> flags; public Or(T x, T y) { this.flags = new List<T>(); this.flags.AddRange(x.ToArray()); this.flags.AddRange(y.ToArray()); } public string Name { get { string[] names = this.flags.OrderBy(f => f.Value).Select(f => f.Name).ToArray(); return String.Join(" ", names); } } public int Value { get { return this.flags.Sum(f => f.Value); } } public string DisplayName { get { string[] names = this.flags.OrderBy(f => f.Value).Select(f => f.DisplayName).ToArray(); return String.Join(" ", names); } } public T[] ToArray() { return this.flags.ToArray(); } public void Add(T x) { this.flags.AddRange(x.ToArray()); } }
Now we are armed to build a custom flags class with minimal effort.
public class Numbers : Flags<Numbers> { public static Numbers None = new Numbers(0, "None"); public static Numbers One = new Numbers(1, "One"); public static Numbers Two = new Numbers(2, "Two"); public static Numbers Four = new Numbers(4, "Four"); private Numbers(int value, string name) : base(value, name, name) { } protected override Numbers ToEnumeration() { return this; } protected override Numbers Or(Numbers x) { return new OredNumbers(this, x); } private class OredNumbers : Numbers { private readonly Or<Numbers> flags; public OredNumbers(Numbers x, Numbers y) : base(-1, String.Empty) { this.flags = new Or<Numbers>(x, y); } public override string Name { get { return this.flags.Name; } } public override string DisplayName { get { return this.flags.DisplayName; } } public override int Value { get { return this.flags.Value; } } protected internal override Numbers[] ToArray() { return this.flags.ToArray(); } protected override Numbers Or(Numbers x) { this.flags.Add(x); return this; } } }
Admittedly this is much more code than you would have to write for simple flags based on an enum
. But the additional power of enumeration classes can also be found in the flags classes. Think of a user selecting from a set of predefined filter criteria. As long as Microsoft does not make expressions serializable you have no way of dynamically building the desired filter on the client side and send it to the server for evaluation other than directly accessing your database (be it SQL or NoSQL) and let some LINQ provider do the translation from expression to native query for you. With flags classes you can combine an abstraction of your filters on the client side, send the flags and let WCF do the reconstruction of the filters on the server side.
The complete source code is available here (project TecX.EnumClasses and the test suite that puts them to use can be found in TecX.EnumClasses.Test). There is a separate solution file TecX.EnumClasses as well.
