Anonymous Implementation Classes – A Design Pattern for C#

博客介绍了C#中实现接口时代码冗长的问题,提出使用“匿名实现类”模式,借助lambda和闭包简洁实现接口。以Linq - to - Lists为例展示其应用,可减少样板代码。同时指出该模式会增加执行时间和内存成本,调试较难,是程序员时间与程序效率的权衡。

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).

内容概要:本文系统介绍了算术优化算法(AOA)的基本原理、核心思想及Python实现方法,并通过图像分割的实际案例展示了其应用价值。AOA是一种基于种群的元启发式算法,其核心思想来源于四则运算,利用乘除运算进行全局勘探,加减运算进行局部开发,通过数学优化器加速函数(MOA)和数学优化概率(MOP)动态控制搜索过程,在全局探索与局部开发之间实现平衡。文章详细解析了算法的初始化、勘探与开发阶段的更新策略,并提供了完整的Python代码实现,结合Rastrigin函数进行测试验证。进一步地,以Flask框架搭建前后端分离系统,将AOA应用于图像分割任务,展示了其在实际工程中的可行性与高效性。最后,通过收敛速度、寻优精度等指标评估算法性能,并提出自适应参数调整、模型优化和并行计算等改进策略。; 适合人群:具备一定Python编程基础和优化算法基础知识的高校学生、科研人员及工程技术人员,尤其适合从事人工智能、图像处理、智能优化等领域的从业者;; 使用场景及目标:①理解元启发式算法的设计思想与实现机制;②掌握AOA在函数优化、图像分割等实际问题中的建模与求解方法;③学习如何将优化算法集成到Web系统中实现工程化应用;④为算法性能评估与改进提供实践参考; 阅读建议:建议读者结合代码逐行调试,深入理解算法流程中MOA与MOP的作用机制,尝试在不同测试函数上运行算法以观察性能差异,并可进一步扩展图像分割模块,引入更复杂的预处理或后处理技术以提升分割效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值