[英]Constrained generics with hierarchy in type parameter
I have a problem with generics in C# I hope you can help me out with. 我在C#中遇到泛型问题我希望你能帮助我。
public interface IElement { }
public interface IProvider<T> where T : IElement {
IEnumerable<T> Provide();
}
So far it's pretty simple. 到目前为止,它非常简单。 I want the providers to return enumerables of specific elements.
我希望提供程序返回特定元素的可枚举。 A specific implementation of the interfaces is as follows:
接口的具体实现如下:
public class MyElement : IElement { }
public class MyProvider : IProvider<MyElement> {
public IEnumerable<MyElement> Provide() {
[...]
}
}
But the problem comes now when I want to use it. 但是当我想要使用它时,问题就出现了。 This does not compile because it cannot implicitly convert
MyProvider
into IProvider<IElement>
: 这不能编译,因为它无法将
MyProvider
隐式转换为IProvider<IElement>
:
IProvider<IElement> provider = new MyProvider();
I have to do a cast to IProvider<IElement>
despite MyProvider
is an IProvider<MyElement>
and MyElement
is an IElement
. 我必须对
IProvider<IElement>
IProvider<MyElement>
尽管MyProvider
是IProvider<MyElement>
而MyElement
是IElement
。 I could avoid the cast by making MyProvider
also implement IProvider<MyElement>
, but why does it not resolve the hierarchy in the type parameter? 我可以通过使
MyProvider
也实现IProvider<MyElement>
来避免IProvider<MyElement>
,但为什么它不解析type参数中的层次结构?
EDIT: As per Thomas's suggestion, we can make it covariant in T
. 编辑:根据托马斯的建议,我们可以使它在
T
协变。 But what if there are other methods like below where there are arguments of type T
? 但是,如果有其他方法,如下面有类型
T
参数?
public interface IProvider<T> where T : IElement {
IEnumerable<T> Provide();
void Add(T t);
}
I have to do a cast to
IProvider<IElement>
despiteMyProvider
is anIProvider<MyElement>
andMyElement
is anIElement
.我必须对
IProvider<IElement>
IProvider<MyElement>
尽管MyProvider
是IProvider<MyElement>
而MyElement
是IElement
。 Why does it not resolve the hierarchy in the type parameter?为什么它不解析类型参数中的层次结构?
This is a very frequently asked question. 这是一个非常常见的问题。 Consider the following equivalent problem:
考虑以下等效问题:
interface IAnimal {}
class Tiger : IAnimal {}
class Giraffe : IAnimal {}
class MyList : IList<Giraffe> { ... }
...
IList<IAnimal> m = new MyList();
Now your question is: "I have to do a cast to IList<IAnimal>
despite the fact that MyList
is an IList<Giraffe>
and Giraffe
is an IAnimal
. Why does this not work?" 现在,你的问题是:“我必须做强制转换为
IList<IAnimal>
尽管MyList
是IList<Giraffe>
和Giraffe
是IAnimal
为什么这个不行?”
It does not work because... suppose it did work: 它不起作用,因为...假设它确实有效:
m.Add(new Tiger());
m is a list of animals. m是动物名单。 You can add a tiger to a list of animals.
您可以将老虎添加到动物列表中。 But m is really a MyList, and a MyList can only contain giraffes!
但是m实际上是MyList,MyList只能包含长颈鹿! If we allowed this then you could add a tiger into a list of giraffes .
如果我们允许这样做,那么你可以将老虎添加到长颈鹿列表中 。
This must fail because IList<T>
has an Add method that takes a T. Now, maybe your interface has no methods that takes a T. In that case, you can mark the interface as covariant , and the compiler will verify that the interface is truly safe for variance and allow the variance you want. 这必须失败,因为
IList<T>
有一个带有IList<T>
的Add方法。现在,也许你的接口没有采用T的方法。在这种情况下,你可以将接口标记为协变 ,编译器将验证接口对于方差是真正安全的,并允许你想要的方差。
Since T
only appears in output position in your IProvider<T>
interface, you can make it covariant in T
: 由于
T
仅出现在IProvider<T>
界面的输出位置,因此可以使其在T
IProvider<T>
协变:
public interface IProvider<out T> where T : IElement {
IEnumerable<T> Provide();
}
This will make this instruction legal: 这将使该指示合法:
IProvider<IElement> provider = new MyProvider();
This feature requires C# 4. Read Covariance and Contravariance in Generics for more details. 此功能需要C#4。阅读泛型中的协方差和逆变量以获取更多详细信息。
If you only use the reference to IProvider<IElement>
to access methods that have T
in an output position, you could segregate the interface into two (please find better names for them, like ISink<in T>
for the contravariant one): 如果你只使用参考
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);
}
Your class implements both: 你的类实现了两个:
public class MyProvider : IProviderOut<MyElement>, IProviderIn<MyElement> {
public IEnumerable<MyElement> Provide() {
...
}
public void Add(MyElement t) {
...
}
}
But now you use the covariant interface when you need to upcast: 但是现在你在需要upcast时使用协变接口:
IProviderOut<IElement> provider = new MyProvider();
Alternatively, your interface can inherit from both: 或者,您的界面可以继承:
public interface IProvider<T> : IProviderIn<T>, IProviderOut<T>
where T : IElement {
// you can add invariant methods here...
}
And your class implements it: 你的类实现它:
public class MyProvider : IProvider<MyElement> ...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.