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

3

11

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?

Sitter answered 18/2, 2016 at 11:51 Comment(3)
Not sure if this discussion is applicable, but it might be.Eyewash
I think, this is related to a question I asked some time back: #6127241Mandy
Did you try delegate TOut F<out TDlgIn, in TDlgOut>(TDlgIn input)? When passing in delegates, the co/contra-variance needs to be the other way around.Postdiluvian
B
1

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>.

Brower answered 18/2, 2016 at 12:58 Comment(0)
P
0

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.

Postdiluvian answered 18/2, 2016 at 13:51 Comment(6)
Yes, this would obviously work. But I want my input to be an input and not input/output swapped. I don't think this will do what I expect. I'd have to try though before I can make a conclusion. Maybe it does what I need, but is just very unintuitive.Sitter
Actually, I cannot use TDlgIn as a parameter, because it is marked as out (co-variant)Sitter
When you think long enough about it, it actually is intuitive (apart from the fact that the whole thing is beyond any intuition...). Your class gets a delegate and usually wants to call it. So to pass in the arguments, it needs to be compatible. When passing in a parameter to the delegate, it is going out of your class and is therefore out. What you get back from the delegate is coming into your class, so it's in.Postdiluvian
You are right (and that explains it quite well). Maybe I need a delegate with 4 type-parameters :) – let's hope C# can infer them, otherwise using that delegate will become really cumbersome.Sitter
I still don't know what you actually want to do.Postdiluvian
It's difficult to explain :) I have an existing class C which takes an Action<C> as parameter (no interface → no variance). I now want to simplify creation of said class by introducing a fluent builder. This builder should accept a Func<TIn, TOut> (class C (sometimes) has input and output fields/arguments which I would then map to TIn/TOut). Problem is, the existing class is somewhat not really well thought out, but I cannot refactor it at the moment.Sitter
A
0
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.

Abortive answered 18/2, 2016 at 15:49 Comment(2)
Additionally, if you want to reverse the types in order to fool the compiler (which actually will just change the output types, so probably won't help you) you could use Func<Func<Tin, TOut>>Abortive
using a Func<Func<>> looks interesting, will give it a shot.Sitter

© 2022 - 2024 — McMap. All rights reserved.