类型参数中具有层次结构的受约束泛型

本文关键字:受约束 泛型 层次结构 类型参数 | 更新日期: 2023-09-27 18:22:21

我在C#中遇到泛型问题,希望你能帮我解决。

public interface IElement { }
public interface IProvider<T> where T : IElement {
    IEnumerable<T> Provide();
}

到目前为止,一切都很简单。我希望提供程序返回特定元素的可枚举值。接口的具体实现如下:

public class MyElement : IElement { }
public class MyProvider : IProvider<MyElement> {
    public IEnumerable<MyElement> Provide() {
        [...]
    }
}

但当我想使用它时,问题就来了。它无法编译,因为它无法将MyProvider隐式转换为IProvider<IElement>:

IProvider<IElement> provider = new MyProvider();

尽管MyProviderIProvider<MyElement>,而MyElementIElement,但我必须对IProvider<IElement>执行强制转换。我可以通过使MyProvider也实现IProvider<MyElement>来避免强制转换,但为什么它不解析类型参数中的层次结构?

编辑:根据托马斯的建议,我们可以使它在T中协变。但是,如果有其他方法,比如下面,其中有类型为T的参数,该怎么办

public interface IProvider<T> where T : IElement {
    IEnumerable<T> Provide();
    void Add(T t);
}

类型参数中具有层次结构的受约束泛型

尽管MyProviderIProvider<MyElement>MyElementIElement,但我必须对IProvider<IElement>执行强制转换。为什么它不解析类型参数中的层次结构?

这是一个经常被问到的问题。考虑以下等效问题:

interface IAnimal {}
class Tiger : IAnimal {}
class Giraffe : IAnimal {}
class MyList : IList<Giraffe> { ... }
...
IList<IAnimal> m = new MyList();

现在你的问题是:"尽管MyListIList<Giraffe>GiraffeIAnimal,但我必须对IList<IAnimal>进行转换。为什么这不起作用?"

它不起作用,因为。。。假设它确实有效:

m.Add(new Tiger());

m是动物列表。你可以将老虎添加到动物列表中。但是m真的是一个MyList,而且MyList只能包含长颈鹿!如果我们允许这样做,那么你可以在长颈鹿列表中添加一只老虎

这一定失败了,因为IList<T>有一个接受T的Add方法。现在,也许你的接口没有接受T的方法。在这种情况下,你可以将接口标记为协变,编译器将验证接口是否真的可以安全地进行方差处理,并允许你想要的方差。

由于T只出现在IProvider<T>接口的输出位置,因此可以在T:中使其协变

public interface IProvider<out T> where T : IElement {
    IEnumerable<T> Provide();
}

这将使该指令合法化:

IProvider<IElement> provider = new MyProvider();

此功能需要C#4。有关更多详细信息,请阅读Generics中的协方差和方差。

如果您使用对IProvider<IElement>的引用来访问在输出位置具有T的方法,则可以将接口分隔为两个(请为它们找到更好的名称,如ISink<in T>用于相反的名称):

public interface IProviderOut<out T> where T : IElement {
  IEnumerable<T> Provide();
}
public interface IProviderIn<in T> where T : IElement {
  void Add(T t);
}

您的类同时实现这两个功能:

public class MyProvider : IProviderOut<MyElement>, IProviderIn<MyElement> {
  public IEnumerable<MyElement> Provide() {
    ...
  }
  public void Add(MyElement t) {
    ...
  }
}

但现在,当您需要上行时,可以使用协变接口:

IProviderOut<IElement> provider = new MyProvider();

或者,您的界面可以从两者继承:

public interface IProvider<T> : IProviderIn<T>, IProviderOut<T> 
  where T : IElement { 
  // you can add invariant methods here...
}

并且你的类实现了它:

public class MyProvider : IProvider<MyElement> ...