3

Anonymous Implementation Classes – A Design Pattern for C#

 3 years ago
source link: http://twistedoakstudios.com/blog/Post774_anonymous-implementation-classes-a-design-pattern-for-c
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Anonymous Implementation Classes – A Design Pattern for C#

posted by Craig Gidney on October 9, 2012

Some interfaces have many simple implementations. IEnumerable is implemented so much that a language feature was introduced to make it easier (iterator methods). However, there are plenty of other interfaces that I often find myself implementing: IDisposable, IComparer<T>, IEqualityComparer<T>, IReadOnlyList<T>, IObservable<T>, etc. But, unfortunately for me, implementing an interface in C# is not concise. You need to define a named class and that class will be mostly boilerplate code. You need to repeat the signatures of the methods you’re implementing, repeat the names of fields multiple times in the constructor, locate the use-once class away from where it is used, etc, etc. Implementing a “single line” method that returns an interface requires a dozen lines of class definition!

In Java this cost would be mitigated by Anonymous Classes, but C# doesn’t have anonymous classes that can implement interfaces (and even in Java they require repeating a lot of signature boilerplate). Luckily, we can use a pattern that I am calling an “Anonymous Implementation Class” that takes advantage of lambdas and closures to implement interfaces very concisely. The idea is simple: create a class that implements interface methods via delegates, then “implement” interfaces by passing in the right delegates.

Note that this pattern is not a new idea. Existing libraries already use it. In fact, I am basing the name on some classes from the Reactive Extensions library (AnonymousObservable/Observer).

Concrete Example: Linq-to-Lists

The latest version of .Net (4.5) includes a new interface: IReadOnlyList (more aptly called a readable list, but I digress), which represents a collection that supports random access to its elements. A readable list must support enumeration, counting, and random access:

public interface IReadOnlyList<out T> : IReadOnlyCollection<T> {
    T this[int index] { get; }
}
public interface IReadOnlyCollection<out T> : IEnumerable<T> {
    int Count { get; }
}
public interface IEnumerable<out T> : IEnumerable {
    IEnumerator<T> GetEnumerator();
}

It so happens that many of the operations we perform on enumerables can be performed on readable lists without losing random access. For example, consider Enumerable.Select, which lazily projects the items in an underlying enumerable (e.g. new[] {1,2,3}.Select(e => e * 2) will enumerate 2 then 4 then 6). But the result of Enumerable.Select is an IEnumerable, not an IReadOnlyList, so if we wanted to see the 1001’th item in a projected list, we’d be forced to enumerate and project the preceding thousand items. To avoid that unnecessary cost, we can implement a readable list variant of Select:

public static IReadOnlyList<TOut> Select<TIn, TOut>(this IReadOnlyList<TIn> list,
                                                    Func<TIn, TOut> projection) {
    return new ReadOnlyListProjection<TIn, TOut>(list, projection);
}
public sealed class ReadOnlyListProjection<TIn, TOut> : IReadOnlyList<TOut> {
    private readonly IReadOnlyList<TIn> _list;
    private readonly Func<TIn, TOut> _projection;
    public ReadOnlyListProjection(IReadOnlyList<TIn> list, Func<TIn, TOut> projection) {
        _list = list;
        _projection = projection;
    }

    public TOut this[int index] {
        get {
            return _projection(_list[index]);
        }
    }
    public int Count {
        get {
            return _list.Count;
        }
    }
    public IEnumerator<TOut> GetEnumerator() {
        return _list.AsEnumerable().Select(_projection).GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}

You might notice that 90% of our implementation of Select is defining the class that implements the interface we want to return. Even worse, most of the class is boilerplate: every implementation of IReadOnlyList<T> is going to implement the non-generic GetEnumerator in the same way, every implementing is going to repeat its field names four times (field declarations, constructor parameter, left and right hand sides of initialization in constructor), and most implementations will delegate methods in a very simple way to an underlying list.

Boilerplate code is bad, because it introduces a lot of opportunities to make a typo or introduce a bug. We can use an anonymous implementation class to reduce the amount of boilerplate code:

public sealed class AnonymousReadOnlyList<T> : IReadOnlyList<T> {
    private readonly Func<int> _count;
    private readonly Func<int, T> _item;
    private readonly IEnumerable<T> _iterator;

    public AnonymousReadOnlyList(Func<int> count, Func<int, T> item, IEnumerable<T> iterator = null) {
        if (count == null) throw new ArgumentNullException("count");
        if (item == null) throw new ArgumentNullException("item");
        this._count = count;
        this._item = item;
        this._iterator = iterator ?? DefaultIterator(count, item);
    }
    private static IEnumerable<T> DefaultIterator(Func<int> count, Func<int, T> item) {
        var n = count();
        for (var i = 0; i < n; i++)
            yield return item(i);
    }

    public int Count {
        get {
            return _count();
        }
    }
    public T this[int index] {
        get {
            return _item(index);
        }
    }
    public IEnumerator<T> GetEnumerator() {
        return _iterator.GetEnumerator();
    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}

Note that this class is not purely “give delegates for each method to be implemented”, although in most cases that’s what anonymous implementation classes are. This class provides conveniences like implementing GetEnumerator in terms of the counter/getter (although you can still provide a specialized one if desired because, for example, many collection enumerators have checks to prevent you from accidentally enumerating them while modifying them, so we’d want to use their enumerator instead of a custom one).

With our anonymous implementation class in hand, we can easily and concisely implement many useful linq-to-lists methods:

public static IReadOnlyList<TOut> Select<TIn, TOut>(this IReadOnlyList<TIn> list,
                                                    Func<TIn, TOut> projection) {
    return new AnonymousReadOnlyList<TOut>(
        count: () => list.Count, 
        item: i => projection(list[i]),
        iterator: list.AsEnumerable().Select(projection));
}
public static IReadOnlyList<TOut> Select<TIn, TOut>(this IReadOnlyList<TIn> list,
                                                    Func<TIn, int, TOut> projection) {
    return new AnonymousReadOnlyList<TOut>(
        count: () => list.Count, 
        item: i => projection(list[i], i),
        iterator: list.AsEnumerable().Select(projection));
}
public static IReadOnlyList<T> Reverse<T>(this IReadOnlyList<T> list) {
    return new AnonymousReadOnlyList<T>(
        count: () => list.Count, 
        item: i => list[list.Count - 1 - i]);
}
public static IReadOnlyList<TOut> Zip<TIn1, TIn2, TOut>(this IReadOnlyList<TIn1> list1,
                                                        IReadOnlyList<TIn2> list2,
                                                        Func<TIn1, TIn2, TOut> projection) {
    return new AnonymousReadOnlyList<TOut>(
        count: () => Math.Min(list1.Count, list2.Count), 
        item: i => projection(list1[i], list2[i]),
        iterator: list1.AsEnumerable().Zip(list2, projection));
}
public static IReadOnlyList<int> Range(this int count) {
    return new AnonymousReadOnlyList<int>(
        count: () => count, 
        item: i => i, 
        iterator: Enumerable.Range(0, count));
}

Each of these methods is very simple, and congruently their implementation is very small. However, note that anonymous implementation classes can handle much more complicated cases thanks to the beauty of closures. For example, mutable state can be in locals instead of fields, as shown in this method that implements an IObservable using AnonymousObservable:

public static IObservable<T> WhenDifferent<T>(this IObservable<T> observable,
                                              IEqualityComparer<T> equality = null) {
    var eq = equality ?? EqualityComparer<T>.Default;
    return new AnonymousObservable<T>(observer => {
        var hasPrev = false;
        var prev = default(T);
        return observable.Observe(
            newValue => {
                if (hasPrev && eq.Equals(prev, newValue)) return;
                hasPrev = true;
                prev = newValue;
                observer.OnNext(prev);
            },
            observer.OnCompleted,
            observer.OnError);
    });
}

There are lots of other opportunities out there for this pattern. For example… every interface I listed in the first paragraph of this post. I can see why Java has it as a language feature (although, humorously, the C# usage I’ve shown is more concise even though it’s not a language feature…).

Tradeoffs

Now that you understand what an anonymous implementation class is (just a class that uses custom delegates to implement an interface), I can discuss the costs of using one instead of defining a new class.

Anonymous implementation classes have an additional layer of indirection when invoking their interface methods. As a result, an additional virtual call is performed and this increases execution time a bit. Also, each instance stores a reference to each delegate it uses to implement an interface (instead of just a single reference to a static vtable of its implementing methods). This means a higher per-instance memory cost, which can be a significant proportional increase depending on the interface and implementation (e.g. ReadOnlyList.Range technically needs only a count, but its anonymous implementation class stores 3 delegate references and the delegates reference a closure containing the count). Finally, note that it’s harder to inspect the contents of an anonymous implementation class (visual studio does a mediocre job visualizing delegates/closures) so debugging methods using them takes some getting used to.

Summary

Anonymous implementation classes are a classic “programmer time vs program efficiency” trade-off. They make your life easier, like using C# instead of C or C instead of assembly. Use them unless you measure that you need the performance (hopefully, in the future, there will be more language and optimization support for them).

View comments on Reddit.

2 Responses to “Anonymous Implementation Classes – A Design Pattern for C#”

  1. af59223e50553525edf303166bac9e5e?s=32&d=mm&r=gMarkus Schaber says:

    It seems that some of the template parameters in the code example got stripped… :-(

    • 2418cf397adaa4f72547f14e61348028?s=32&d=mm&r=gCraigGidney says:

      Ah, good catch. Sometimes I miss a few (I have to replace
      with & l t ;, & g t ; so they don’t get mistaken for html). I think I
      caught the remaining ones.


Twisted Oak Studios offers consulting and development on high-tech interactive projects. Check out our portfolio, or Give us a shout if you have anything you think some really rad engineers should help you with.

Archive


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK