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()' signature
的Expected a method with 'RetVal GetRetValT()' signature
编译错误。
那么,有什么方法可以解决这个问题,还是有一种替代方法可以用来创建我的字符串……generics方法调用映射?
C#仅允许接口上的协方差。 这意味着您无法自动将RetVal
为RetVal
。 如果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的经典协方差/逆变。 您假设因为MyDerived1
和MyDerived2
inheritance自MyBase
, RetVal
inheritance自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