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”的隐式引用转换。

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

4

2 回答 2

7

TinIC<T>必须是IB<IA>. 你给了它一个IB<A>. 您无法保证 anIB<A>可以用作IB<IA>仅因为Aimplements IA

可以这样想:ifIB<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您可以通过如下标记使其合法outinterface IB<out T>. 但是,T在任何“输入上下文”中使用都是非法的。特别是,您不能拥有任何T具有 setter 的类型的属性。如果我们做出这个限制,那么问题就会消失,因为a_val不能设置为实例,Orange 因为它是只读的

这个问题在 SO 上被问得非常频繁。在 C# 中查找有关“协变和逆变”的问题,以获得大量示例。

于 2013-03-04T19:32:58.960 回答
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”的隐式引用转换。

这真是一个很酷的功能,不是吗?

于 2013-03-04T19:30:30.457 回答