C#中泛型类的特化模式是什么?

10
在C#中,有时我希望能够为特定的泛型类"实例化"创建特殊方法。
更新:以下代码只是更抽象问题的愚蠢示例 - 不要过于关注时间系列,只需为某些T添加额外的方法的原则。
示例:
class Timeseries<T> 
{ 
    ...
    TimeSeries<T> Slice(...) { ... }
}

如果 T 是 double,我希望有一些额外的方法,比如 Integrate()Interpolate() 等,这些方法只对 double 有意义,因为我需要对它们进行算术运算。

有几种方法可以实现这个目的,但我找不到满意的方法。

1. 继承到一个特殊的类中

class TimeseriesDouble : Timeseries<double>
{ 
    double Interpolate(...) { ... }
    ...
}

缺点:TimeseriesDouble.Slice()会返回一个新的Timeseries<double>对象,现在缺少了我的特殊方法。

2. 外部方法

public static double Interpolate(Timeseries<double> ts, ...) { ... }

缺点:违反了面向对象的原则。我不想将我的方法放在其他地方。此外,这些方法可能需要私有/受保护状态。

3. 扩展方法

与2相同,只是具有更好的调用语法。

4. 共同基类

class TimeSeries_base { ... }
class TimeSeries<T> : TimeSeries_base { .. typesafe versions of methods .. }
class TimeSeriesDouble : TimeSeries_base { .. typesafe versions of methods .. }

缺点: TimeSeries_base 中的许多内容都被复制到了两个子类中。基类可能只成为子类实用函数的占位符。

优点: 我现在可以动态地执行像List<TimeSeries_base>这样的操作。

5. 忘记一个公共类

即,在代码中将Timeseries<T>TimeseriesDouble分开。

缺点: 然后我就无法充分利用将TimeseriesDouble视为TimeSeries<T>的所有好处,例如使用ZIP(A,B)组合两个时序,其中一个恰好是双倍。


其他想法? 目前,我认为我最喜欢设计(1)。


给出更多客户端代码的具体示例以及时间序列是什么可能会很有用。如果您不这样做,您将会得到许多与主题无关的回答,因为没有足够的信息可以供使用,并且人们会进行假设。 - Robert Paulson
例如,我不明白为什么(2)不能返回TimeSeries<double>,或者如果这是有效的,为什么我不能有public static T Interpolate(Timeseries<T> ts, ...) { ... }等。我能有一个TimeSeries<AnyOldClass>吗? - Robert Paulson
是的,也许例子应该更具体一些。但我认为这只是一个更一般的设计问题的例子。所以也许我应该更抽象地表达问题,这样时间序列就不会分散注意力了。 - Bjarke Ebert
4个回答

12
您可以始终使用自引用泛型技巧:
public class TimeSeries<T, U> where U : TimeSeries<T, U>
{
    U Slice(...)
}

public class TimeSeriesDouble : TimeSeries<double, TimeSeriesDouble>
{
    ...
}

它可能有点难理解,但是它可以工作。


在我的具体库中,我认为我会选择更直接的东西。不过这个技巧很不错,我会收藏起来的 :-) - Bjarke Ebert
但是如果您想扩展 TimeSeriesDouble,仍然会有一个问题。那么子类的 Slice 方法将返回一个 TimeSeriesDouble 而不是子类型的实例。我会使用 F# 和模式匹配来处理这种类型的结构。 - SRKX

9
interface ITimeSeries<T> { ... }

abstract class TimeSeriesBase<TS> where TS : TimeSeriesBase<TS> 
 { public TS Slice() { ... } 
 }

class TimeSeries<T>:TimeSeriesBase<TimeSeries<T>>,ITimeSeries<T> {}

class TimeSeriesDouble:TimeSeriesBase<TimeSeriesDouble>,ITimeSeries<double>
 { public double Interpolate() { ... }
 }

太完美了!这正是我所想的。 - Neil
这也是我使用的模式。 - Alexey Romanov
使用这种方法与Jon Skeet发布的方法相比有什么好处呢?它们几乎是一样的,不是吗? - SOReader
通过这种方法,您可以将TimeSeriesDoubleTimeSeries<double>都视为ITimeSeries<double>,这正是OP想要的,例如,将它们Zip在一起。 - Mark Cidade

1
我会选择(1)并根据需要进行转换。
 TimeSeriesDouble tsD = new TimeSeriesDouble();
 TimeSeriesDouble subTSD = tsD.Slice(...) as TimeSeriesDouble;

是的,我喜欢这样的解决方案。但要注意:.Slice()(TimeSeries<T>中的一个方法)现在必须实际返回一个TimeSeriesDouble,因此它必须是虚拟的或使用其他技巧。 - Bjarke Ebert

0
我会考虑实现一个工厂来返回适当的子类。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接