类型参数中具有层次结构的受约束泛型
本文关键字:受约束 泛型 层次结构 类型参数 | 更新日期: 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();
尽管MyProvider
是IProvider<MyElement>
,而MyElement
是IElement
,但我必须对IProvider<IElement>
执行强制转换。我可以通过使MyProvider
也实现IProvider<MyElement>
来避免强制转换,但为什么它不解析类型参数中的层次结构?
编辑:根据托马斯的建议,我们可以使它在T
中协变。但是,如果有其他方法,比如下面,其中有类型为T
的参数,该怎么办
public interface IProvider<T> where T : IElement {
IEnumerable<T> Provide();
void Add(T t);
}
尽管
MyProvider
是IProvider<MyElement>
,MyElement
是IElement
,但我必须对IProvider<IElement>
执行强制转换。为什么它不解析类型参数中的层次结构?
这是一个经常被问到的问题。考虑以下等效问题:
interface IAnimal {}
class Tiger : IAnimal {}
class Giraffe : IAnimal {}
class MyList : IList<Giraffe> { ... }
...
IList<IAnimal> m = new MyList();
现在你的问题是:"尽管MyList
是IList<Giraffe>
,Giraffe
是IAnimal
,但我必须对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> ...