Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 641
Richard Ev Avatar asked May 11 '12 15:05

Richard Ev


People also ask

What are generics and which problem do they solve in Swift?

Swift Generics allows us to create a single function and class (or any other types) that can be used with different data types. This helps us to reuse our code.

How to define a generic function in Swift?

The generic version of the function uses a placeholder type name (called T , in this case) instead of an actual type name (such as Int , String , or Double ). The placeholder type name doesn't say anything about what T must be, but it does say that both a and b must be of the same type T , whatever T represents.


4 Answers

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>
        };
like image 120
StriplingWarrior Avatar answered Oct 03 '22 08:10

StriplingWarrior


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> { ... }
like image 45
Chris Shain Avatar answered Oct 03 '22 09:10

Chris Shain


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>
        };
    }
}
like image 27
phoog Avatar answered Oct 03 '22 07:10

phoog


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>
like image 40
Dave Bish Avatar answered Oct 03 '22 09:10

Dave Bish