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 в результате метода в вашем интерфейсе.