Co/contravariance with Func<in T1, out TResult> as parameter

465 views Asked by At

Assume I have an interface such as

public interface IInterface<in TIn, out TOut> {
  IInterface<TIn, TOut> DoSomething(TIn input);
}

TIn being contra-variant, and TOut being co-variant.

Now, I want callers to be able to specify some function to be executed on the input's value, so naïvely I would add the following method to the interface:

IInterface<TIn, TOut> DoSomethingWithFunc(Func<TIn, TOut> func);

which … does not work. TIn is now required to be covariant, and TOut contravariant.

I understand, that I cannot use covariant generic types as input for methods, but I thought I could use them in a nested generic type which itself specifies the variance (Func<in T1, out TResult>).

I tried creating a new delegate type with co-/contravariant types and change the interface to accept an argument of this type, to no avail (same error).

public delegate TOut F<in TDlgIn, out TDlgOut>(TDlgIn input);

public interface IInterface<in TIn, out TOut> {
  IInterface<TIn, TOut> DoSomethingWithFunc(F<TIn, TOut> func);
}

I there a way I can make the compiler happy? Is this even possible (for instance with other nested types, or additional generic arguments)? If not, why not?

3

There are 3 answers

0
Lee On BEST ANSWER

This would not be safe since you could then use it to do:

public class Id<I, O> : IInterface<I, O>
{
    private Func<I, O> f;
    public Id(Func<I, O> f) { this.f = f; }
    public IInterface<I, O> DoSomething(I i) { this.f(i); return this; }
    public IInterface<I, O> DoSomethingWithFunc(Func<I, O> newF) {
        this.f = newF;
        return this;
    }
}

and then

Func<Animal, string> fa;
IInterface<object, string> oi = new Id<object, string>(_ => "");
Interface<Monkey, string> mi = oi;  //safe
IInterface<Monkey, string> mi2 = mi.DoSomethingWithFunc(fa);
oi.DoSomething("not an animal!");

at this point you will have passed a string to an Func<Animal, string>.

6
Stefan Steinegger On

Did you try this?

delegate TOut F<out TDlgIn, in TDlgOut>(TDlgIn input)

When passing in delegates, the co/contra-variance needs to be the other way around. I don't know if it helps. No idea what you probably want to do in the method.

2
Jakub Szumiato On
Error   CS1961  Invalid variance: The type parameter 'TIn' must be covariantly valid on 'IInterface<TIn, TOut>.DoSomethingWithFunc(Func<TIn, TOut>)'. 'TIn' is contravariant.

what you actually are trying to do, is you're passing co-variant TOut type as an argument to the 'DoSomethingWithFunc' method. That's not possible, Only In types can only be passed as arguments, Out only as results. In your example, you put TOut as an argument (it will be passed to 'DoSomethingWithFunc' as TOut is the result of your Func).

There are a lot of articles all over the web about it (what does it mean to be 'covariantly valid', but I think the best explanatory one is: https://blogs.msdn.microsoft.com/ericlippert/2009/12/03/exact-rules-for-variance-validity/

This means you could, of course put your Func as a result of the method in your interface.