泛型、继承和类型转换

4

我的问题与在泛型类型中转换类有关。虽然我意识到将对象如 List<string> 转换为 List<object> 需要支持协变以防止将 object 对象添加到包含字符串的列表中,但我想知道为什么编译器不接受下面的转换,并且是否可以使用接口和协变或逆变来解决这个问题:

public class TypeA<T> {}

public class TypeB<T> {}

public class TypeC : TypeB<int> {}

class Program
{
    public static void MyMethod<OutputType>(TypeA<TypeB<OutputType>> Parameter) {}

    static void Main(string[] args)
    {
        TypeA<TypeC> Test = new TypeA<TypeC>();
        MyMethod<int>(Test);
    }
}

编译此代码会产生错误:
参数1:无法将“ConsoleApplication1.TypeA<ConsoleApplication1.TypeC>”转换为“ConsoleApplication1.TypeA<ConsoleApplication1.TypeB<int>>”。
尽管TypeC是TypeB<int>的直接后代。
6个回答

3
正如其他评论者所指出的那样,尽管 TypeC 是从 TypeB<int> 派生出来的,但 TypeA<TypeC> 不是从 TypeA<TypeB<int>> 派生出来的。然而,你可以通过向 MyMethod 添加一个额外的类型参数来使代码正常运行:
public static void MyMethod<DerivedB,OutputType>(TypeA<DerivedB> Parameter)
    where DerivedB : TypeB<OutputType> {}

3

我对你的问题感到困惑,因为问题的前言显示你已经知道答案了。你不能将List<string>转换为List<object>,因为这样就可以将长颈鹿添加到实际上是字符串列表的对象列表中,这是不安全的。

如果你用"List"替换"TypeA",用"object"替换"TypeB<int>",用"string"替换"TypeC",那么你已经把自己置于一个已知不起作用的情况下。并非编译器神奇地知道List的某些事情,只是不允许List的情况 - 相反,编译器不允许任何这样的变化。

在C# 4中,我们添加了一些已知在变量下是类型安全的接口和委托类型的变量。这是一种“选择加入”的模式 - 你必须向编译器证明你是安全的,只有这样我们才会让你使用变量。


2

既然C# 4.0支持泛型接口的协变,以下示例可能解决问题:

public interface ITypeA<out T> {}

public class TypeA<T> : ITypeA<T> {}

public class TypeB<T> {}

public class TypeC : TypeB<int> {}

class Program
{
    public static void MyMethod<OutputType>(ITypeA<TypeB<OutputType>> Parameter) {}

    static void Main(string[] args)
    {
        ITypeA<TypeC> Test = new TypeA<TypeC>();
        MyMethod<int>(Test);
    }
}

请注意,类型T只能用于ITypeA中的方法返回值。

事实证明,我对接口与协变性的工作方式理解不足。正如Eric在他的回答中指出的那样,TypeB和TypeC类似于List转换案例。类似这样的解决方案实际上解决了我的具体问题,非常感谢。 - Mark A.

2

又一个泛型协变问题 - 请看我在这里发布的答案。 TypeA<TypeC>不以任何方式继承自TypeA<TypeB<int>>


0

但是 ConsoleApplication1.TypeA<ConsoleApplication1.TypeC> 并没有继承自 ConsoleApplication1.TypeA<ConsoleApplication1.TypeB<int>>


0
假设我有一个List对象。然后我将其转换为List。List有一个名为“add”的方法,它需要一个T参数。我添加了一个Apple到它里面(因为我有一个List)。 然而,在某些地方,我的代码仍然期望List,所以List的一些成员实际上是apples!!!
因此,即使X继承自Y,TypeA也不能被强制转换为TypeA。

你如何知道一个苹果是T类型? - recursive
那么X和Y是什么?实际上我一点也不理解这些。 - recursive
本来这只是一个说明为什么继承不是一个好主意的例子。你不能将List<Orange>强制转换为List<Fruit>,就像你不能将List<X>(无论X是什么)强制转换为List<Y>一样,即使X扩展了Y。 在这种情况下,T是一个泛型类型,并且我正在使用List<Apple>。 - luiscubal

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