通用接口层次结构

4

我有一些通用接口相互链接。

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”不能用作泛型类型或方法“IC”的类型参数“T”。从“b”到“IB”的隐式引用转换不存在。

在这种情况下,我该如何正确使用泛型接口?

2个回答

7

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#中"协变和逆变"的问题,可以找到大量的例子。


引用蒙提·派森的话:“我的大脑疼痛!” - RBarryYoung
@RBarryYoung:你会想去读这篇文章的:http://blogs.msdn.com/b/ericlippert/archive/2007/10/24/5529183.aspx(请注意,这个系列是在C#4.0添加协变和逆变之前编写的,所以我使用符号“<-T>”表示“<in T>”。当我编写这个系列时,我们还没有决定语法。) - Eric Lippert
哎呀!我肯定不想成为被指派去维护这种东西的初级程序员。 :) - RBarryYoung

3

我不知道是否有更简单(且更加干净)的方法,但是这段代码可以编译:

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”类型不能用作泛型类型或方法“ConsoleApplication2.IC”中的类型参数“T”。从“ConsoleApplication2.b”到“ConsoleApplication2.IB”没有隐式引用转换。
这真是一个很酷的功能,不是吗?

+1,对于这个问题你深入了解了一下。使用泛型时,实际上需要链接特定性。 - Mike Perrenoud

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接