I posted awhile ago about a pattern for ad-hoc extensions in .NET using generics. Unfortunately, like every "design pattern", you had to manually ensure that your abstraction properly implements the pattern. There was no way to have the compiler enforce it, like conforming to an interface.
It's common wisdom that "design patterns" are simply a crutch for languages with insufficient abstractive power. Fortunately, .NET's multicast delegates provides the abstractive power we need to eliminate the design pattern for ad-hoc extensions:
/// <summary> /// Dispatch cases to handlers. /// </summary> /// <typeparam name="T">The type of the handler.</typeparam> public static class Pattern<T> { static Dispatcher<T> dispatch; static Action<T, object> any; delegate void Dispatcher<T>(T func, object value, Type type, ref bool found); /// <summary> /// Register a case handler. /// </summary> /// <typeparam name="T0">The argument type.</typeparam> /// <param name="match">Expression dispatching to handler.</param> public static void Case<T0>(Expression<Action<T, T0>> match) { var call = match.Body as MethodCallExpression; var handler = Delegate.CreateDelegate(typeof(Action<T, T0>), null, call.Method) as Action<T, T0>; dispatch += (T x, object o, Type type, ref bool found) => { // if type matches exactly, then dispatch to handler if (typeof(T0) == type) { found = true; handler(x, (T0)o); } }; } /// <summary> /// Catch-all case. /// </summary> /// <param name="match">Expression dispatching to handler.</param> public static void Any(Expression<Action<T, object>> match) { var call = match.Body as MethodCallExpression; var handler = Delegate.CreateDelegate(typeof(Action<T,object>), null, call.Method) as Action<T, object>; any += handler; } /// <summary> /// Dispatch to a handler for <typeparamref name="T0"/>. /// </summary> /// <typeparam name="T0">The value type.</typeparam> /// <param name="value">The value to dispatch.</param> /// <param name="func">The dispatcher.</param> public static void Match<T0>(T0 value, T func) { bool found = false; dispatch(func, value, value.GetType(), ref found); if (!found) { if (any == null) throw new KeyNotFoundException( "Unknown type."); else any(func, value); } } }
The abstraction would be used like this:
interface IFoo { void Bar(int i); void Foo(char c); void Any(object o); } class xFoo : IFoo { public void Bar(int i) { Console.WriteLine("Int: {0}", i); } public void Foo(char c) { Console.WriteLine("Char: {0}", c); } public void Any(object o) { Console.WriteLine("Any: {0}", o); } } static void Main(string[] args) { Pattern<IFoo>.Case<int>((x, i) => x.Bar(i)); Pattern<IFoo>.Case<char>((x, i) => x.Foo(i)); Pattern<IFoo>.Match(9, new xFoo()); Pattern<IFoo>.Match('v', new xFoo()); try { Pattern<IFoo>.Match(3.4, new xFoo()); } catch (KeyNotFoundException) { Console.WriteLine("Not found."); } Pattern<IFoo>.Any((x, o) => x.Any(o)); Pattern<IFoo>.Match(3.4, new xFoo()); // prints: // Int: 9 // Char: v // Not found. // Any: 3.4 }
Unlike the previous pattern for ad-hoc extensions, dispatching is always precise in that it dispatches to the handler for the value's dynamic type. The previous solution dispatched only on the static type. This can also be a downside, but you could easily extend the Match method to test on subtypes as well.
The other downside of this solution is that it's not quite as fast since all the type tests are run on each dispatch, where the previous solution cached the specific delegate in a static generic field. This caching can be added to the above class as well. Then, you can have the best of both worlds if you happen to know that the static type is the same as the dynamic type.
Comments
So you can define a visitor interface that matches on Int32 and IFormattable, should you ever need such a bizarre pairing, and dispatching to the correct method to handle that case happens for you. This yields comparable extensibility as simple type classes.
As I mentioned on reddit, you can even extend this to symmetric multiple dispatch.
You can see the intended use there. You can now write parametric functions over any kind of type, and implement custom, extensible functions over those types without having to write wrapper classes and dispatching logic.
The generic visitor is as efficient as it can be, and it's only about 100 lines of code.