Skip to main content

Reusable Ad-Hoc Extensions for .NET

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

Qwertie said…
I'm not understanding what the purpose or goal is here.
Sandro Magi said…
You can define an ad-hoc visitor interface without the type being involved in dispatching, as it is with the visitor pattern. Hence, ad-hoc.

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.
Sandro Magi said…
Since I was going to add it eventually anyway, I went ahead and did implemented a simple generic visitor for Dynamics.NET.

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.

Popular posts from this blog

async.h - asynchronous, stackless subroutines in C

The async/await idiom is becoming increasingly popular. The first widely used language to include it was C#, and it has now spread into JavaScript and Rust. Now C/C++ programmers don't have to feel left out, because async.h is a header-only library that brings async/await to C! Features: It's 100% portable C. It requires very little state (2 bytes). It's not dependent on an OS. It's a bit simpler to understand than protothreads because the async state is caller-saved rather than callee-saved. #include "async.h" struct async pt; struct timer timer; async example(struct async *pt) { async_begin(pt); while(1) { if(initiate_io()) { timer_start(&timer); await(io_completed() || timer_expired(&timer)); read_data(); } } async_end; } This library is basically a modified version of the idioms found in the Protothreads library by Adam Dunkels, so it's not truly ground bre...

Easy Automatic Differentiation in C#

I've recently been researching optimization and automatic differentiation (AD) , and decided to take a crack at distilling its essence in C#. Note that automatic differentiation (AD) is different than numerical differentiation . Math.NET already provides excellent support for numerical differentiation . C# doesn't seem to have many options for automatic differentiation, consisting mainly of an F# library with an interop layer, or paid libraries . Neither of these are suitable for learning how AD works. So here's a simple C# implementation of AD that relies on only two things: C#'s operator overloading, and arrays to represent the derivatives, which I think makes it pretty easy to understand. It's not particularly efficient, but it's simple! See the "Optimizations" section at the end if you want a very efficient specialization of this technique. What is Automatic Differentiation? Simply put, automatic differentiation is a technique for calcu...

Building a Query DSL in C#

I recently built a REST API prototype where one of the endpoints accepted a string representing a filter to apply to a set of results. For instance, for entities with named properties "Foo" and "Bar", a string like "(Foo = 'some string') or (Bar > 99)" would filter out the results where either Bar is less than or equal to 99, or Foo is not "some string". This would translate pretty straightforwardly into a SQL query, but as a masochist I was set on using Google Datastore as the backend, which unfortunately has a limited filtering API : It does not support disjunctions, ie. "OR" clauses. It does not support filtering using inequalities on more than one property. It does not support a not-equal operation. So in this post, I will describe the design which achieves the following goals: A backend-agnostic querying API supporting arbitrary clauses, conjunctions ("AND"), and disjunctions ("OR"). Implemen...