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

C# 类型参数中具有层次结构的受约束泛型,c#,generics,types,parameters,hierarchy,C#,Generics,Types,Parameters,Hierarchy,我对C#中的泛型有问题,希望你能帮我解决这个问题 公共接口IElement{} 公共接口i提供程序,其中T:IElement{ IEnumerable Provide(); } 到目前为止,这很简单。我希望提供程序返回特定元素的枚举。 接口的具体实现如下所示: 公共类MyElement:IEElement{} 公共类MyProvider:IProvider{ 公共IEnumerable提供(){ [...] } } 但是当我想用它的时候,问题来了。由于无法将MyProvider隐式转换为IP

我对C#中的泛型有问题,希望你能帮我解决这个问题

公共接口IElement{}
公共接口i提供程序,其中T:IElement{
IEnumerable Provide();
}
到目前为止,这很简单。我希望提供程序返回特定元素的枚举。 接口的具体实现如下所示:

公共类MyElement:IEElement{}
公共类MyProvider:IProvider{
公共IEnumerable提供(){
[...]
}
}
但是当我想用它的时候,问题来了。由于无法将
MyProvider
隐式转换为
IProvider
,因此无法编译:

IProvider provider=new MyProvider();
尽管
MyProvider
是一个
IProvider
MyElement
是一个
IElement,但我必须对
IProvider
进行转换。我可以通过使
MyProvider
也实现
IProvider
来避免强制转换,但是为什么它不解析类型参数中的层次结构呢

编辑:根据托马斯的建议,我们可以在
T
中使其协变。但是,如果有下面这样的其他方法,其中存在类型为
T

公共接口i提供程序,其中T:IElement{
IEnumerable Provide();
无效添加(T);
}

由于
T
仅出现在
i提供程序
界面的输出位置,您可以在
T
中使其协变:

public interface IProvider<out T> where T : IElement {
    IEnumerable<T> Provide();
}
公共接口i提供程序,其中T:IElement{
IEnumerable Provide();
}
这将使本指令合法化:

IProvider<IElement> provider = new MyProvider();
IProvider provider=new MyProvider();
此功能需要C#4。阅读更多细节

尽管
MyProvider
是一个
IProvider
MyElement
是一个
IElement,但我必须对
IProvider
进行转换。为什么它不解析类型参数中的层次结构

这是一个非常常见的问题。考虑下面的等价问题:

interface IAnimal {}
class Tiger : IAnimal {}
class Giraffe : IAnimal {}
class MyList : IList<Giraffe> { ... }
...
IList<IAnimal> m = new MyList();
m是一个动物列表。您可以将老虎添加到动物列表中。但是m实际上是一个MyList,MyList只能包含长颈鹿!如果我们允许这样做,那么你可以在长颈鹿的名单中添加一只老虎

这一定会失败,因为
IList
有一个采用T的Add方法。现在,可能您的接口没有采用T的方法。在这种情况下,您可以将接口标记为协变,编译器将验证接口是否确实安全,并允许您所需的差异。

如果您仅使用对
i提供程序的引用来访问在输出位置具有
T
的方法,则可以将接口分为两个(请为它们找到更好的名称,如
ISink
为逆变型):

公共接口i提供,其中T:IElement{
IEnumerable Provide();
}
公共接口i提供,其中T:IElement{
无效添加(T);
}
您的类实现了以下两个方面:

public class MyProvider : IProviderOut<MyElement>, IProviderIn<MyElement> {
  public IEnumerable<MyElement> Provide() {
    ...
  }
  public void Add(MyElement t) {
    ...
  }
}
公共类MyProvider:IProviderOut、IProviderIn{
公共IEnumerable提供(){
...
}
公共无效添加(MyElement t){
...
}
}
但现在,当您需要向上投射时,可以使用协变界面:

IProviderOut<IElement> provider = new MyProvider();
IProviderOut provider=new MyProvider();
或者,您的接口可以从以下两者继承:

public interface IProvider<T> : IProviderIn<T>, IProviderOut<T> 
  where T : IElement { 
  // you can add invariant methods here...
}
公共接口IProvider:IProviderIn、IProviderOut
其中T:IElement{
//您可以在此处添加不变方法。。。
}
而您的类实现了它:

public class MyProvider : IProvider<MyElement> ...
公共类MyProvider:IPProvider。。。

非常感谢,这解决了这个问题,但是当
IProvider
本身扩展了另一个接口时会发生什么?请参见上面的编辑。@caerolus,对于C#IProvider和IProvider是不同的类型。这个设计决定是为了防止不正确的上转换。请简洁地说明您有IProvider,然后将其上转换到IProvider,然后再上转换到IProvider和向下转换ed到IProvider-boom,例外。很抱歉,我看不出这两者之间有什么关系。是的,它们是不同的类型,我不能向下转换到IProvider,因为我假设Mylement2与MyElement无关。但是为什么我要从IProvider向上转换到IProvider呢?@caerolus,根据您的编辑:如果
t
仅显示在输出位置,则inteFace可以是协变的;它
T
只出现在输入位置,接口可以是逆变的;但是它
T
在输入和输出位置,接口都不能是可变的…@thomasleveque所以这不是一个简单的解决方案,对吗?我应该让MyProvider也实现IProvider还是在任何地方都使用强制转换?这可能会必须被.net 3.5用户使用,所以也许实现IProvider更好?谢谢!@EricLippet非常感谢您的解释。我理解正确,是的,非常有意义。谢谢!我想我现在明白了协方差逆变换的全部内容。以前从未听说过它,但再一次,我从未实现过我的泛型。
public interface IProvider<T> : IProviderIn<T>, IProviderOut<T> 
  where T : IElement { 
  // you can add invariant methods here...
}
public class MyProvider : IProvider<MyElement> ...