A List<> of Func<>s, compile error with generic return type, but why?

霸气de小男生 提交于 2019-12-04 22:56:32

C# only allows covariance on interfaces. That means you cannot cast a RetVal<MyDerived1> to a RetVal<MyBase> automatically. If RetVal should be covariant, create an interface for it, like so:

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;
    }
}

Then this code will work:

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

The problem is the classic covariance/contravariance of generics. You are assuming that because MyDerived1 and MyDerived2 inherit from MyBase, that a RetVal<MyDerived1> inherits from RetVal<MyBase>, and it doesn't.

The easiest way to fix this is probably to change the code to:

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

or better yet, as JS points out in comments, just change RetVal<T> to be covariant if possible:

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

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

Generic type parameters for classes cannot be covariant, but they can be covariant for interfaces. You can do what you want with an interface IRetVal<T> instead of the class RetVal<T>, if the type parameter is declared as covariant. In order to declare an interface type parameter as covariant, it must be used only in "output" positions.

To illustrate, this code will not compile:

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

To get the code to compile, you must either remove the out modifier from the type parameter, or remove the AcceptValue method (because it uses T for a parameter: an input position).

With the IRetVal<out T> interface, you can do this:

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>
        };
    }
}
Dave Bish

I had a similar issue just recently.

C# does not support return type covariance for the purposes of interface implementation or virtual method overrding. See this question for details:

Does C# support return type covariance?.

You may be able to hack this, my doing:

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

//Then, change this to:
test.GetRetValT<MyDerived1, MyBase>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!