我在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();
尽管IProvider<IElement>
是MyProvider
且IProvider<MyElement>
是MyElement
,但我必须对IElement
进行投射。我可以通过使MyProvider
同时实现IProvider<MyElement>
来避免强制转换,但为什么它不能解析类型参数中的层次结构?
编辑:根据Thomas的建议,我们可以在T
中使其协变。但是,如果存在类型为T
的参数的其他方法,那么该怎么办?
public interface IProvider<T> where T : IElement {
IEnumerable<T> Provide();
void Add(T t);
}
答案 0 :(得分:8)
尽管
IProvider<IElement>
是MyProvider
且IProvider<MyElement>
是MyElement
,但我必须对IElement
进行投射。为什么它不能解析类型参数中的层次结构?
这是一个非常常见的问题。请考虑以下等效问题:
interface IAnimal {}
class Tiger : IAnimal {}
class Giraffe : IAnimal {}
class MyList : IList<Giraffe> { ... }
...
IList<IAnimal> m = new MyList();
现在你的问题是:“尽管IList<IAnimal>
是MyList
且IList<Giraffe>
是Giraffe
,我仍需要对IAnimal
进行投射为什么这不起作用?“
它不起作用,因为......假设它确实有效:
m.Add(new Tiger());
是一个动物名单。您可以将老虎添加到动物列表中。但是m实际上是MyList,MyList只能包含长颈鹿!如果我们允许这样做,那么你可以将老虎添加到长颈鹿列表中。
这必定会失败,因为IList<T>
有一个带有T的Add方法。现在,也许你的接口没有采用T的方法。在这种情况下,你可以将接口标记为 covariant ,并且编译器将验证该接口对于方差是否真正安全,并允许您想要的方差。
答案 1 :(得分:6)
由于T
仅出现在IProvider<T>
界面的输出位置,因此您可以在T
中对其进行协变:
public interface IProvider<out T> where T : IElement {
IEnumerable<T> Provide();
}
这将使该指示合法:
IProvider<IElement> provider = new MyProvider();
此功能需要C#4。有关详细信息,请阅读Covariance and Contravariance in Generics。
答案 2 :(得分:2)
如果仅使用对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> ...