我是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
方法中静态调度调用。但是,我无法弄清楚哪个是这个问题的最佳解决方案。
答案 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)
可能有更好的答案,但这是我的看法。
让我们说A
和B
都会在其中实现具有属性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
而不是编写泛型方法(您的类变为泛型而不是其方法)。它必须在语义上有意义,因此我无法真正告诉您的具体情况(类名称被混淆)。