接口通用层次结构

本文关键字:层次结构 接口 | 更新日期: 2023-09-27 18:02:26

我有一些通用接口彼此链接。

public interface IA
{
    int val { get; set; }
}
public interface IB<T> where T:IA
{
    T a_val { get; set; }
}
public interface IC<T> where T : IB<IA>
{
    T b_val { get; set; }
}
public class a:IA
{
    public int val { get; set; }
}
public class b:IB<a>
{
    public a a_val { get; set; }
}
public class c:IC<b>
{
    public b b_val { get; set; }
}

对于最后一个类c,我有一个错误:

类型"b"不能用作泛型类型中的类型参数"T"或方法"IC"。没有来自"b"的隐式引用转换至"IB"。

在这种情况下,如何正确使用通用接口?

接口通用层次结构

IC<T>中的T必须是IB<IA>。你给了它一个IB<A>。不能仅仅因为A实现了IA就保证IB<A>可以用作IB<IA>

可以这样想:如果IB<T>的意思是"我可以吃任何T型的东西",IA的意思是"我是一种水果",A的意思是苹果",那么IB<IA>的意思是:"我可以吃任意水果",而IB<A>的意思是我可以吃任何苹果"。如果某个代码想要喂你香蕉和葡萄,那么它需要IB<IA>,而不是IB<A>

让我们假设IB<A>可以转换为IB<IA>,看看哪里出了问题:

class AppleEater : IB<Apple> 
{  
    public Apple a_val { get; set; }
}
class Apple : IA 
{
    public int val { get; set; }
}
class Orange : IA 
{
    public int val { get; set; }
}
...
IB<Apple> iba = new AppleEater();
IB<IA> ibia = iba; // Suppose this were legal. 
ibia.a_val = new Orange(); // ibia.a_val is of type IA and Orange implements IA

现在,我们只需将iba.val(Apple类型的属性(设置为对Orange类型对象的引用。

这就是为什么转换必须是非法的。

那么,你如何使这合法化呢?

就目前的代码而言,你不能,因为正如我刚才所展示的,它不是类型安全的。

您可以将T标记为out,使其合法,如下所示:interface IB<out T>。但是,在任何"输入上下文"中使用T都是非法的。特别是,不能有任何具有setter的T类型的属性。如果我们进行该限制,那么问题就会消失,因为a_val不能设置为Orange的实例,因为它是只读的

这个问题在SO上被问得非常频繁。请在C#中查找有关"协方差和反方差"的问题,以获取大量示例。

我不知道是否可以更容易地完成(以及更多的clean(,但这段代码编译:

public interface IA
{
    int val { get; set; }
}
public interface IB<T> where T : IA
{
    T a_val { get; set; }
}
public interface IC<T, U> where T : IB<U> where U : IA
{
    T b_val { get; set; }
}
public class a : IA
{
    public int val { get; set; }
}
public class b : IB<a>
{
    public a a_val { get; set; }
}
public class c : IC<b, a>
{
    public b b_val { get; set; }
}

更重要的是,它不会让你做这样的事情:

public class a1 : IA
{
    public int val { get; set; }
}
public class c : IC<b, a1>
{
    public b b_val { get; set; }
}

编译器抛出以下错误:

类型"ConsoleApplication2.b"不能用作类型参数"T"在泛型类型或方法"ConsoleApplication2.IC"中没有从"ConsoleApplication2.b"到的隐式引用转换'控制台应用程序2.IB'.

这真的很酷,不是吗?