This is a bit of a lengthy question, so please bear with me.
I need to create a mapping between a set of strings and corresponding generic method calls for each string. However I've run into a compile issue, explained lower down.
In my scenario I am using a Dictionary<>
, but the issue exists equally for a List<>
. For simplicity I'm using a List<>
in the example below.
Consider these three classes:
public abstract class MyBase { /* body omitted */ }
public class MyDerived1 : MyBase { /* body omitted */ }
public class MyDerived2 : MyBase { /* body omitted */ }
And a method in some other class:
public class Test
{
public T GetT<T>() where T : MyBase { /* body omitted */ }
}
In another class, I can declare a List<Func<MyBase>>
like this:
public class SomeClass
{
public void SomeFunc()
{
var test = new Test();
var list1 = new List<Func<MyBase>>
{
test.GetT<MyDerived1>,
test.GetT<MyDerived2>
};
}
}
This is all fine and well.
But, what if I want to have a function that return a generic class like this:
public class RetVal<T> where T : MyBase { /* body omitted */ }
public class Test
{
public RetVal<T> GetRetValT<T>() where T : MyBase
{
return null;
}
}
And I want to create an equivalent List<>
using this function. i.e. a 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
};
}
}
I get compile errors of Expected a method with 'RetVal<MyBase> GetRetValT()' signature
.
So, is there any way around this, or is there an alternative approach that I can use for creating my string...generic method call mappings?
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>
};
}
}
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>
来源:https://stackoverflow.com/questions/10554435/a-list-of-funcs-compile-error-with-generic-return-type-but-why