Func 的List ,使用generics返回类型编译错误,但为什么呢?

这是一个冗长的问题,所以请耐心等待。

我需要在一组字符串和每个字符串的相应generics方法调用之间创建一个映射。 但是,我遇到了编译问题,向下解释。

在我的场景中,我使用的是Dictionary ,但问题对于List同样存在。 为简单起见,我在下面的示例中使用List

考虑这三个类:

 public abstract class MyBase { /* body omitted */ } public class MyDerived1 : MyBase { /* body omitted */ } public class MyDerived2 : MyBase { /* body omitted */ } 

还有一些其他类的方法:

 public class Test { public T GetT() where T : MyBase { /* body omitted */ } } 

在另一个类中,我可以像这样声明List<Func>

 public class SomeClass { public void SomeFunc() { var test = new Test(); var list1 = new List<Func> { test.GetT, test.GetT }; } } 

这一切都很好。

但是,如果我想要一个返回如下通用类的函数,该怎么办:

 public class RetVal where T : MyBase { /* body omitted */ } public class Test { public RetVal GetRetValT() where T : MyBase { return null; } } 

我想使用此函数创建一个等效的List 。 即列表>>?

 public class Class1 { public void SomeFunc() { var test = new Test(); var list2 = new List<Func<RetVal>> { test.GetRetValT, // compile error test.GetRetValT // compile error }; } } 

我得到Expected a method with 'RetVal GetRetValT()' signatureExpected a method with 'RetVal GetRetValT()' signature编译错误。

那么,有什么方法可以解决这个问题,还是有一种替代方法可以用来创建我的字符串……generics方法调用映射?

C#仅允许接口上的协方差。 这意味着您无法自动将RetValRetVal 。 如果RetVal应该是协变的,为它创建一个接口,如下所示:

 public interface IRetVal { } public class RetVal : IRetVal where T : MyBase { /* body omitted */ } public class Test { public IRetVal GetRetValT() where T : MyBase { return null; } } 

然后这段代码将起作用:

  var list2 = new List>> { test.GetRetValT, test.GetRetValT }; 

问题是generics的经典协方差/逆变。 您假设因为MyDerived1MyDerived2inheritance自MyBaseRetValinheritance自RetVal ,但它没有。

解决此问题的最简单方法可能是将代码更改为:

 var list2 = new List>> { () => (MyBase)test.GetRetValT, () => (MyBase)test.GetRetValT }; 

或者更好,正如JS在评论中指出的那样,如果可能的话,只需将RetVal更改为协变:

 public interface IRetVal { ... } public class RetVal : IRetVal { ... } 

类的generics类型参数不能是协变的,但它们对于接口可以是协变的。 如果将type参数声明为covariant可以使用接口IRetVal而不是RetVal类来执行RetVal 为了将接口类型参数声明为协变,它必须仅在“输出”位置使用。

为了说明,这段代码不会编译:

 interface IRetVal { T Value { get; } void AcceptValue(T value); } 

要获取要编译的代码,必须从type参数中删除out修饰符,或者删除AcceptValue方法(因为它使用T作为参数:输入位置)。

使用IRetVal界面,您可以执行以下操作:

 public class MyBase { } public class MyDerived1 : MyBase { } public class MyDerived2 : MyBase { } public interface IRetVal where T : MyBase { /* body omitted */ } public class Test { public IRetVal GetRetValT() where T : MyBase { return null; } } public class Class1 { public void SomeFunc() { var test = new Test(); var list = new List>> { test.GetRetValT, test.GetRetValT }; } } 

最近我遇到了类似的问题。

出于接口实现或虚拟方法覆盖的目的,C#不支持返回类型协方差。 有关详细信息,请参阅此问

C#是否支持返回类型协方差? 。

你可能会破解这个,我这样做:

 public RetVal GetRetValT() where T : MyBase where R : MyBase { return null; } //Then, change this to: test.GetRetValT