一个List <Func键的> <> S,编译错误与通用的返回类型,但是为什

2019-07-29 09:06发布

这是一个有点冗长的问题,所以请多多包涵。

我需要创建一个组字符串并为每个字符串对应的一般方法调用之间的映射。 不过我碰到一个编译的问题,解释低了下去。

在我的方案,我用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<T>() where T : MyBase { /* body omitted */ }
}

在另一类,我可以声明一个List<Func<MyBase>>这样的:

public class SomeClass
{
    public void SomeFunc()
    {
        var test = new Test();

        var list1 = new List<Func<MyBase>>
            {
                test.GetT<MyDerived1>,
                test.GetT<MyDerived2>
            };
    }
}

这是一切优秀和良好。

但是,如果我想要什么有返回泛型类这样的功能:

public class RetVal<T> where T : MyBase { /* body omitted */ }

public class Test
{
    public RetVal<T> GetRetValT<T>() where T : MyBase
    {
        return null;
    }
}

我想创建一个等效List<>使用此功能。 即列表>>?

public class Class1
{
    public void SomeFunc()
    {
        var test = new Test();

        var list2 = new List<Func<RetVal<MyBase>>>
            {
                test.GetRetValT<MyDerived1>, // compile error
                test.GetRetValT<MyDerived2> // compile error
            };
    }
}

我得到编译的误差Expected a method with 'RetVal<MyBase> GetRetValT()' signature

那么,有没有解决这个办法,或者是有另一种方法,我可以用于创建我的字符串...泛型方法调用映射?

Answer 1:

C#只允许在接口上的协方差。 这意味着你可以不投一个RetVal<MyDerived1>RetVal<MyBase>自动。 如果RetVal应该是协变的,创建一个接口为它,就像这样:

public interface IRetVal<out T>
{

}
public class RetVal<T> : IRetVal<T> where T : MyBase { /* body omitted */ }

public class Test
{
    public IRetVal<T> GetRetValT<T>() where T : MyBase
    {
        return null;
    }
}

那么这个代码将工作:

    var list2 = new List<Func<IRetVal<MyBase>>>
        {
            test.GetRetValT<MyDerived1>,
            test.GetRetValT<MyDerived2>
        };


Answer 2:

问题是仿制药的经典协方差/逆变。 我们假定你是因为MyDerived1MyDerived2继承MyBase ,一个RetVal<MyDerived1>从继承RetVal<MyBase>并且它没有。

解决这个问题的最简单的方法可能是更改代码以:

var list2 = new List<Func<RetVal<MyBase>>>
        {
            () => (MyBase)test.GetRetValT<MyDerived1>,
            () => (MyBase)test.GetRetValT<MyDerived2>
        };

或者更好的,因为JS在评论中指出的,只是改变RetVal<T>被协变如果可能的话:

public interface IRetVal<out T> { ... }

public class RetVal<T> : IRetVal<T> { ... }


Answer 3:

上课泛型类型参数不能协变,但它们可以被协变的接口。 你可以做你想做一个接口是什么IRetVal<T>而不是类RetVal<T> 如果该类型参数被声明为协变。 为了声明一个接口类型参数作为协变,它必须仅在“输出”的位置被使用。

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

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

获得代码进行编译,则必须移除out从类型参数修改,或移除AcceptValue方法(因为它使用T代表一个参数:输入位置)。

随着IRetVal<out T>接口,你可以这样做:

public class MyBase { }
public class MyDerived1 : MyBase { }
public class MyDerived2 : MyBase { }

public interface IRetVal<out T> where T : MyBase { /* body omitted */ }

public class Test
{
    public IRetVal<T> GetRetValT<T>() where T : MyBase
    {
        return null;
    }
}

public class Class1
{
    public void SomeFunc()
    {
        var test = new Test();

         var list = new List<Func<IRetVal<MyBase>>> 
        { 
            test.GetRetValT<MyDerived1>,
            test.GetRetValT<MyDerived2>
        };
    }
}


Answer 4:

我有一个类似的问题刚刚结束。

C#不支持接口的实现或虚方法overrding的目的,返回类型协方差。 详情请参见这个问题:

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

您可能能够破解这个,我做的:

public RetVal<R> GetRetValT<T,R>() where T : MyBase where R : MyBase
{
    return null;
}

//Then, change this to:
test.GetRetValT<MyDerived1, MyBase>


文章来源: A List<> of Func<>s, compile error with generic return type, but why?