我可以将基本 class 类型作为通用参数传递给接口吗

Can I pass a base class type as a generic parameter to an interface

我能否将基本 class 类型作为通用参数传递给在 C# 中该基本 class 的后继上下文中实现的接口。

让我们假设有一个接口:

public interface IElement<T>
{
   Collection<T> Neighbors { get; }   
}

我想这样实现这个接口:

public class Element : IElement<object>
{
   public Collection<Neighbor> Neighbors { get; set; }
}

我什至也试过这样实现:

public class Element : IElement<IObject>
{
   public Collection<Neighbor> Neighbors { get; set; }
}

IObject 和 Neighbor 所在位置:

public interface IObject
{ 
}

public class Neighbor : IObject
{
   // some props
}

无论哪种方式,我都会得到相同的错误: 元素未实现 IElement 接口 - 成员 Neighbors 无法实现 IElement.Neighbors,因为它们具有不同的 return 类型。

这样做的目的是对数据类型进行抽象并在业务逻辑中使用它们的更简单版本:

public class BusinessLogic
{
   bool predicate1;
   bool predicate2;
    
   // variant 1
   public bool CanIDoSomething(IElement<object> element)
   {
      return element.Neighbours.Any() && predicate1 && predicate2;
   }

   // variant 2
   public bool CanIDoSomething(IElement<IObject> element)
   {
      return element.Neighbours.Any() && predicate1 && predicate2;
   }
}

如果我尝试使用 IEnumerable 而不是 Collection,问题会变得更深:

public interface IElement<T>
{
   IEnumerable<T> Neighbors { get; }   
}

我错过了什么,是否有解决方法?


编辑:

我将 IElement 接口通用参数协变如下:

public interface IElement<out T>
{
   IEnumerable<T> Neighbors { get; }
}

但这并没有解决接口实现错误

让 class 正确实现接口的唯一方法是这样做:

public class Element : IElement<IObject>
{
   public Collection<Neighbor> Neighbors { get; set; }

   IEnumerable<IObject> IElement<IObject>.Neighbors => Neighbors;
}

是否存在导致在实现接口时不隐式应用协方差的限制?

有没有更优雅的方法?

我想保留 IObject 接口,而不是直接使用 object,因为在 IObject 接口中,如果需要,我可以放置 Neighbor class.

原始场景Collection<T>

你得到这个错误,因为你的 IElement<T> 接口有一个通用类型参数 T 而 属性 NeighborsCollection<T> 类型。因此,使用具体类型 object 实现 IElementElement 类型需要实现 Collection<object> 而不是 Collection<Neighbor>。此 属性 必须是 public,因为它是从您的 public 界面实现的,并且 class 中的默认成员可见性是 private。如果将 object 替换为 Neighbor 或任何其他类型,这同样适用。请参阅此示例 Neighbor

public class Element : IElement<Neighbor>
{
    public Collection<Neighbor> Neighbors { get; set; }
}

虽然这解决了您原来的错误,但您将无法将类型 Element 的实例传递给您的方法 CanIDoSomething,因为您的类型参数 TIElement<T>invariant,表示只能使用原来指定的类型。在示例中,此类型为 Neighbor 而不是 IObjectobject.

您需要将类型参数设置为 covariant 才能使用更派生的类型,例如 Neighbor。但是,您会 运行 遇到 Collection<T> 也使用不变类型参数的问题,因此您需要将其替换为具有协变类型参数的类型,例如 IReadOnlyCollection<T>IEnumerable<T>.下面的示例将向您展示协方差如何适用于您的案例。您可以从那里开始使用其他类型和集合。

具有泛型和 IEnumerable<T>

的场景

如果您希望 IElement<T> 是通用的并使用 IEnumerable<T>,请使用关键字 out.

声明具有协变类型参数的接口
public interface IElement<out T>
{
    IEnumerable<T> Neighbors { get; }
}

然后在您的具体 class 中使用正确的类型实现它,例如Neighbor.

public class Element : IElement<Neighbor>
{
    public IEnumerable<Neighbor> Neighbors { get; }
}

您可以保持两个 CanIDoSomething 方法不变,因为 Neighbor 派生自 object 并实现了 IObject 接口,因此它将与两种情况下的协方差。