减少c#中代码复制的惯用方法

时间:2014-05-27 09:16:22

标签: c# templates generics design-patterns

我是C#的新手(我来自C ++),我遇到了一个简单的模式,在C ++中我会使用模板解决,但同样的方法不能使用C#泛型。

下一个代码(C#与C ++模板的混合)显示了我的问题。

class A { /* ... */ }
class B { /* ... */ }
// C, D, ...

class W
{
    public void Update(A a) { /* ... */ }
    public void Update(B b) { /* ... */ }
    // C, D, ...
}

class X
{
    template <typename T>
    public void Update(IEnumerable<T> vs) {
        if (vs.any(vs => CreateOrUpdate(v))) {
            doStuff();
        }
    }

    template <typename T>
    public void Update(T v)
    {
        if (CreateOrUpdate(v)) {
            doStuff()
        }
    }

    template <typename T>
    private bool CreateOrUpdate(T v)
    {
        W w;
        bool updated = false;
        if (!m.TryGetValue(v.Id, out w)) {
            w = new W(v.Id);
            m.Add(w.Id, w);
            updated = true;
        }
        return w.Update(v) || updated;
    }

    private Dictionary<string, W> m;
}

即使A, B, ...实施了interface IId { string Id { get; } }之类的界面并且我使用了通用public void Update<T>(IEnumerable<T> vs) : where T : IId,代码也无法正常工作w.Update(v)(它需要添加public bool Update(IId id) 1}}在W中,但是这个方法将是总是被调用的方法。

从理论的角度来看,我理解C#泛型和C ++模板之间的区别,以及为什么w.Update(v)无法从Update W方法中静态调度调用。但是,我无法弄清楚哪个是这个问题的最佳解决方案。

2 个答案:

答案 0 :(得分:2)

确实有一种方法是将方法public bool Update(IId id)添加到类W,但您需要动态调度参数id

class W
{
    public bool Update(IId id)
    {
        dynamic d_id = id;
        return Update(d_id);
    }

    // ...
}

程序将在运行时确定要调用哪个Update函数,因为d_id被声明为dynamic

如果您添加另一个实现E的类IId,但忘记实现Update(E e),则上述Update函数将递归调用自己,直到您内存不足为止。您可以通过将Update(IId id)重命名为DoUpdate(IId id)并在DoUpdate中调用CreateOrUpdate来避免无限递归的危险。然后,您将收到一个有意义的异常,指出无法分派id类型的E

MSDN上有一个很好的blog post on multimethods in C#可能对您有帮助。

答案 1 :(得分:2)

可能有更好的答案,但这是我的看法。

让我们说AB都会在其中实现具有属性IId的接口Id。我会在接口中添加一个方法Update(W w),以便在这些类中实现,就像你写的那样:

private bool CreateOrUpdate(T v)
{
    W w;
    bool updated = false;
    if (!m.TryGetValue(v.Id, out w)) {
        w = new W(v.Id);
        m.Add(w.Id, w);
        updated = true;
    }
    return v.Update(w) || updated;
}

通过这种方式,您可以将每个案例视为相同的,这基本上是接口点(而不是使用typeof开关和糟糕的性能反射)。在我看来,这是一个更好的OOP方法来解决您的问题。这称为Inversion of Control

顺便说一下,你可以写:

 class X<T> where T : IId

而不是编写泛型方法(您的类变为泛型而不是其方法)。它必须在语义上有意义,因此我无法真正告诉您的具体情况(类名称被混淆)。