使用指定类型实现通用接口

5

我有一个有趣的情况,我想使用一个基类,利用类型参数来实现接口,并通过继承类保持代码简洁。

public interface ICalculator
{
    void Process(ICalculationModel calculationModel);
}

public abstract class CalculatorBase<T> :ICalculator where T : ICalculationModel
{
     // Compiler moans that Process(ICalculationModel calculationModel) isn't implemented
     public abstract void Process(T calculationModel);
}

public class PipeworkInspections : CalculatorBase<GasSafetyModel>
{
    public override void Process(GasSafetyModel documentModel){
        //snip
    }
}

有没有什么我在通用的“where”子句或其他方面遗漏的东西?在我的想法中,这应该可以工作。或者编译器需要与接口定义完全相同的实现吗?
我不能轻易地将类型参数移动到ICalculator中,因为有很多地方使用它而不需要泛型。
那就清楚了。感谢您提供的信息。现在显然的解决方案是让接口带上类型参数。但是,ICalculator在许多地方都被使用,并且只被引用为“ICalculator”,如果我在引用ICalculator的接口中省略类型参数,则会出现编译器错误...是否有一种应该有效的架构方式?

我认为它必须与“ICalculator”中的内容完全相同。然而,由于我主要从事Java开发而不是C#,所以我不能确定。 - Powerlord
这是行不通的,因为你可以这样做 ICalculator c = new PipeworkInspections(); c.Process(new OtherModel()) ,这是不安全的。你最好的做法是实现 ICalculator.Process,然后在泛型实现中进行转换。 - Lee
5个回答

8

在我脑海里,这应该可以工作。

问题在于你的想法! :-) 这是不可行的。让我们看看为什么。

interface ICage
{
    void Enclose(Animal animal);
}
class ZooCage<T> : ICage where T : Animal
{
    public void Enclose(T t) { ... }
}
...

var giraffePaddock = new ZooCage<Giraffe>();
var cage = (ICage)giraffePaddock;
var tiger = new Tiger();
icage.Enclose(tiger);

现在长颈鹿围栏里有一只老虎,对于老虎来说生活很美好,但对于长颈鹿来说却很糟糕。这就是为什么这是非法的。

或者编译器是否需要与接口定义完全相同的实现?

实现接口成员的成员必须完全匹配所实现方法的签名。例如,您不能使用返回类型协变:

interface I
{
    Animal GetAnimal();
}
class C : I
{
    public Giraffe GetAnimal() { ... } // not legal.
}

合同要求一个动物,你提供了一只长颈鹿。从逻辑上讲应该可以,但在C#中这是不合法的。(在C++中则合法。)

有关返回类型协变性的原因,请参见本网站上的许多问题。

参数类型逆变也是如此。

interface I
{
    void PutMammal (Mammal mammal);
}
class C : I
{
    public PutMammal(Animal animal) { ... } // not legal.
}

再次说明,逻辑上说得通;合同要求您带一只哺乳动物,而这里带来的是任何动物。但这并不合法。

C#中存在一些协变和逆变操作;可以查看本站关于这些主题的众多问题,或者浏览ericlippert.com或我的以前的msdn博客上的协变和逆变文章。


你知道有哪些语言允许接口的实现(或道德等效物)在方法参数方面是逆变的吗?我听说过一些具有返回类型协变性的语言(你刚提到了C ++),但没有参数逆变性。 - Servy
@Servy:也许是Eiffel?我记得听说过Eiffel有这个功能。不过我从未使用过Eiffel,所以不能确定。当然,在将方法转换为委托时,C#允许参数逆变性,这在某种程度上可以看作是一种道德等价物;你可以将委托视为一个名为Invoke的单方法接口。 - Eric Lippert
@Servy:我查了一下。Eiffel支持不安全参数协变,这就是为什么它一直在我脑海中。Sather是一种类似于Eiffel的语言,支持参数逆变。 - Eric Lippert

0
如果这个工作正常,那么你应该能够像这样说:
PipeworkInspections pipeworks = new PipeworkInspections();
ICalculator calculator = pipeworks;

NuclearPowerSafetyModel nuclearModel = new NuclearPowerSafetyModel();
calculator.Process(nuclearModel); // <-- Oops!

这可能不是你想要的...


0

你的接口说明任何实现它的类将会提供这个方法:

void Process(ICalculationModel calculationModel);

显然,PipeworkInspections并没有。它没有一个接受任何ICalculationModelProcess方法。它只有一个接受ICalculationModel特定实现的方法。因此,您的编译失败了。


0

是的,你需要精确的实现。

作为替代方案,如果适用于您,您可以使interfaceProcess方法通用:

public interface ICalculator<T> where T : ICalculationModel
{
    void Process(T calculationModel);
}

public abstract class CalculatorBase<T> : ICalculator where T : ICalculationModel
{
    public abstract void Process(T calculationModel);
}

0

我同意Eric Lippert的回答:你不能这样做。他非常好地解释了为什么会发生这种情况。

如果你真的想这样做,你可以在抽象类中添加以下内容,它将编译通过:

void ICalculator.Process(ICalculationModel calcMod)
{
   Process((T)calcMod);
}

但是你需要知道你在做什么,否则你可能会在运行时遇到一些InvalidCastException


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