泛型和接口问题

3

给定以下类和接口:

public class Test<T>
{
}

public interface ITesting<T>
{
    Test<T> LoadTest();
}

我可以通过以下方式添加一个类:

public class TestManager : ITesting<object>
{
    #region ITesting

    public Test<object> LoadTest()
    {
        return new Test<object>();
    }

    #endregion
}

这段代码运行正常,没有出现任何错误。如果我将该类替换为以下内容:

public class TestDerived : Test<object>
{
}

public class TestDerivedManager : ITesting<object>
{
    #region ITesting

    public TestDerived LoadTest()
    {
        return new TestDerived();
    }

    #endregion
}

我现在遇到了以下错误:
Error   'TestDerivedManager'没有实现接口成员 'ITesting.LoadTest()'。因为它没有匹配的返回类型 'Test',所以'TestDerivedManager.LoadTest()'无法实现'ITesting.LoadTest()'。
但是对我来说,这应该是可以工作的,因为TestDerivedTest<object>。显然我理解错了什么,有没有可能有人指出为什么这是不正确的?也可能指出我可能怎样才能纠正它吗?

3
请注意,这种行为与泛型完全无关。如果你有interface I { Animal M(); } class C : I { public Giraffe M() { return new Giraffe(); } },你将会得到相同的错误。 - Eric Lippert
5个回答

6
一个 TestDerived 是一个 Test<object>,但是一个 Test<object> 不是一个 TestDerived
协定规定 ITesting<object> 有一个方法可以返回任何 Test<object>,而不仅仅是某个特殊情况(TestDerived)。
要使您的代码正常工作,请按以下方式更改它:
public class TestDerivedManager : ITesting<object>
{
    public Test<object> LoadTest()
    {
        return new TestDerived();
    }
}

6
您所需的功能称为返回类型协变。它是 C++ 的一个特性,但不是 C# 的,所以您需要按照错误提示操作。
我建议您创建一个显式接口实现来调用您的公共方法。这样,您可以兼顾两者的优点。

4
将方法的返回类型改回Test<object>,这样它就可以正常工作了。当实现接口时,返回类型是合同的一部分。您仍然可以返回TestDerived,因为它是一个Test<object>

2
可以,但我认为它没有解释为什么当TestDerived实际上是Test<Object>时它不起作用。 - cgatian

3
你所寻找的叫做“返回类型协变”,不幸的是,它在C#中不被支持。即使该返回类型来源于原始返回类型,你也不能覆盖(或实现)具有不同返回类型的方法。
你可以这样做(可能是个好主意,也可能不是),显式地实现接口并调用一个公共方法,该方法返回你所需的更窄类型:
public class TestDerived : Test<object>
{
}

public class TestDerivedManager : ITesting<object>
{
  public TestDerived LoadTest()
  {
    return new TestDerived();
  }

  Test<object> ITesting<object>.LoadTest()
  {
    return this.LoadTest();
  }
}

这给您的实现增加了额外的复杂性,但也为您提供了您所需的公共契约以及与接口的兼容性。


0

每个人都有很好的技术答案,但没有人解释为什么。所以我想我会给出一个关于接口的例子。

接口编程的一般方式是你应该实现接口中描述的确切方法或属性。假设你有一个接口:

public interface ITesting<T>
{
    Test<T> LoadTest();
}

在您的消费者中使用此接口应该是这样的:

ITesting<object> testingLoader = GetTestingLoader();
Test<object> testingLoader.LoadTest();

如果您使用接口访问对象,则不知道其实现方式。您只知道接口ITesting<T>将为LoadTest()方法返回Test<T>

当您查看上面的消费者示例时,它就会变得有意义。除非声明,否则您(和编译器)不会知道接口ITesting<object>具有Test<object> LoadTest()方法。如果尝试使用已实现ITesting<object>但没有Test<object> LoadTest()方法的对象会发生什么?它会出错,对吧?

这遵循LSV原则


嗯,不,你错了。谈到LSP:我应该能够将TestDerivedManager对象视为ITesting<object>,在其上调用LoadTest并获得一个Test<object>(如接口所指定的)。实现返回一个TestDerived,它是一个Test<object>。所以一切都很好,没有LSP违规,代码应该正常工作。没有错误。唯一的注意事项是:有些语言支持它,但C#不支持。 - Daniel A.A. Pelsmaeker

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