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

Jul*_*ano 6 c# generics parameters types hierarchy

我在C#中遇到泛型问题我希望你能帮助我.

public interface IElement { }

public interface IProvider<T> where T : IElement {
    IEnumerable<T> Provide();
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,它非常简单.我希望提供程序返回特定元素的可枚举.接口的具体实现如下:

public class MyElement : IElement { }

public class MyProvider : IProvider<MyElement> {
    public IEnumerable<MyElement> Provide() {
        [...]
    }
}
Run Code Online (Sandbox Code Playgroud)

但是当我想要使用它时,问题就出现了.这不会编译,因为它无法隐式转换MyProviderIProvider<IElement>:

IProvider<IElement> provider = new MyProvider();
Run Code Online (Sandbox Code Playgroud)

我必须做一个演员,IProvider<IElement>尽管MyProvider是一个IProvider<MyElement>并且MyElement是一个IElement.我可以通过制作MyProvider实现来避免强制转换IProvider<MyElement>,但为什么它不能解析类型参数中的层次结构?

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

public interface IProvider<T> where T : IElement {
    IEnumerable<T> Provide();
    void Add(T t);
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 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();
Run Code Online (Sandbox Code Playgroud)

现在你的问题是:"我必须做一个演员,IList<IAnimal>尽管这MyList是一个IList<Giraffe>并且Giraffe是一个IAnimal.为什么这不起作用?"

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

m.Add(new Tiger());
Run Code Online (Sandbox Code Playgroud)

m是动物名单.您可以将老虎添加到动物列表中.但是m实际上是MyList,MyList只能包含长颈鹿!如果我们允许这样做,那么你可以将老虎添加到长颈鹿列表中.

这必须失败,因为IList<T>有一个带有T的Add方法.现在,也许你的接口没有采用T的方法.在这种情况下,你可以将接口标记为协变,编译器将验证接口是否真正安全方差并允许您想要的方差.


Tho*_*que 6

由于T只出现在IProvider<T>界面的输出位置,因此可以使其在T以下位置变为协变:

public interface IProvider<out T> where T : IElement {
    IEnumerable<T> Provide();
}
Run Code Online (Sandbox Code Playgroud)

这将使该指示合法:

IProvider<IElement> provider = new MyProvider();
Run Code Online (Sandbox Code Playgroud)

此功能需要C#4.阅读泛型中的协方差和逆变量以获取更多详细信息.


Jor*_*dão 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);
}
Run Code Online (Sandbox Code Playgroud)

你的类同时实现了:

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

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

IProviderOut<IElement> provider = new MyProvider();
Run Code Online (Sandbox Code Playgroud)

或者,您的接口可以从两者继承:

public interface IProvider<T> : IProviderIn<T>, IProviderOut<T> 
  where T : IElement { 
  // you can add invariant methods here...
}
Run Code Online (Sandbox Code Playgroud)

你的类实现了它:

public class MyProvider : IProvider<MyElement> ...
Run Code Online (Sandbox Code Playgroud)