为何C#中无法添加返回不同泛型参数Zoo的函数到列表?
泛型委托协变导致的编译错误及解决方法
场景复现
你定义了如下C#类型:
public interface IAnimal { /* ... */ } public class Dog : IAnimal { /* ... */ } public class Cat : IAnimal { /* ... */ } public class Zoo<T> where T : IAnimal { /* ... */ }
随后尝试创建返回特定泛型Zoo的函数,并将这些函数加入List<Func<Zoo<IAnimal>>>:
public Zoo<Dog> CreateZooOfDog() { /* ... */ } public Zoo<Cat> CreateZooOfCat() { /* ... */ } var zooes = new List<Func<Zoo<IAnimal>>>(); Func<Zoo<Dog>> zooOfDog = () => program.CreateZooOfDog(); Func<Zoo<Cat>> zooOfCat = () => program.CreateZooOfCat(); zooes.Add(zooOfDog); // 编译错误 zooes.Add(zooOfCat); // 编译错误
错误原因
核心问题是泛型类的不变性:
Zoo<T>作为泛型类,默认是不变的——哪怕Dog实现了IAnimal,Zoo<Dog>和Zoo<IAnimal>也是完全独立的类型,无法隐式转换。- 由此延伸,
Func<Zoo<Dog>>和Func<Zoo<IAnimal>>是不同的委托类型,自然不能直接加入同一个列表。 - 改成
List<object>能成功,是因为所有类型都继承自object,但这种做法会彻底丢失类型安全,属于不规范的临时方案。
解决方法
方法1:将Zoo<T>改为协变泛型接口
把Zoo<T>的核心抽象改为协变接口(用out关键字标记类型参数),协变允许将派生类型的泛型接口转换为基类型的泛型接口:
// 定义协变接口 public interface IZoo<out T> where T : IAnimal { /* ... */ } // 让Zoo类实现该接口 public class Zoo<T> : IZoo<T> where T : IAnimal { /* ... */ }
然后将列表类型改为List<Func<IZoo<IAnimal>>>:
var zooes = new List<Func<IZoo<IAnimal>>>(); Func<IZoo<Dog>> zooOfDog = () => program.CreateZooOfDog(); Func<IZoo<Cat>> zooOfCat = () => program.CreateZooOfCat(); zooes.Add(zooOfDog); // 正常编译 zooes.Add(zooOfCat);
方法2:显式包装委托转换返回值
如果不想修改Zoo<T>的结构,可以手动包装委托,显式转换返回值:
var zooes = new List<Func<Zoo<IAnimal>>>(); // 包装委托,显式转换返回值 zooes.Add(() => (Zoo<IAnimal>)program.CreateZooOfDog()); zooes.Add(() => (Zoo<IAnimal>)program.CreateZooOfCat());
注意:这种方式需要确保
Zoo<T>的实际实现支持该转换,否则可能在运行时抛出强制转换异常。
方法3:使用非泛型基类/接口
给Zoo<T>定义一个非泛型的基类或接口,比如IZoo,让Zoo<T>实现它:
public interface IZoo { /* 定义非泛型公共方法 */ } public class Zoo<T> : IZoo where T : IAnimal { /* ... */ }
然后将列表类型改为List<Func<IZoo>>:
var zooes = new List<Func<IZoo>>(); zooes.Add(() => program.CreateZooOfDog()); zooes.Add(() => program.CreateZooOfCat());
该方式适合仅需调用非泛型方法的场景。
内容的提问来源于stack exchange,提问作者user2877989
相关产品推荐
相关产品推荐

