Co/контравариантность с Func <in T1, out TResult> как параметр

Предположим, что у меня есть интерфейс, такой как

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

TIn является контравариантным, а TOut является ковариантным.

Теперь я хочу, чтобы вызывающие абоненты могли указать какую-либо функцию, которая будет выполняться на входном значении, поэтому наивно я добавлю следующий метод в интерфейс:

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

который... не работает. TIn теперь необходимо ковариантно, а TOut контравариантно.

Я понимаю, что я не могу использовать ковариантные общие типы в качестве ввода для методов, но я думал, что могу использовать их во вложенном родовом типе, который сам определяет дисперсию (Func<in T1, out TResult>).

Я попробовал создать новый тип делегата с ко-контравариантными типами и изменить интерфейс, чтобы принять аргумент этого типа, безрезультатно (такая же ошибка).

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

Я могу сделать компилятор счастливым? Возможно ли это (например, с другими вложенными типами или дополнительными генерическими аргументами)? Если нет, почему бы и нет?

Ответ 1

Это не было бы безопасно, поскольку вы могли бы использовать его:

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

а затем

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!");

в этот момент вы пройдете string до Func<Animal, string>.

Ответ 2

Вы пробовали это?

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

При передаче делегатам, co/contra-дисперсия должна быть наоборот. Я не знаю, помогает ли это. Не знаю, что вы, вероятно, захотите сделать в методе.

Ответ 3

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

то, что вы на самом деле пытаетесь сделать, заключается в том, что вы передаете ко-вариант TOUT типа в качестве аргумента метода DoSomethingWithFunc. Это невозможно, только типы In могут передаваться только как аргументы, Out только как результаты. В вашем примере вы помещаете TOUT в качестве аргумента (он будет передан "DoSomethingWithFunc", поскольку TOUT является результатом вашего Func).

В нем много статей по всему миру (что значит быть "ковариантно действительным", но я считаю, что лучший объяснительный: https://blogs.msdn.microsoft.com/ericlippert/2009/12/03/exact-rules-for-variance-validity/

Это означает, что вы могли бы, конечно, поместить свой Func в результате метода в вашем интерфейсе.