逆变/协变在使用 Func<in T1, out TResult> 作为参数时的应用

11

假设我有这样一个接口:

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

有没有办法让编译器满意?这是否可能(例如使用其他嵌套类型或额外的泛型参数)?如果不行,为什么?


不确定 这个讨论 是否适用,但可能有帮助。 - GSerg
我认为这与我一段时间前提出的一个问题有关: https://dev59.com/EFfUa4cB1Zd3GeqPJJHB - MattC
你尝试过使用 delegate TOut F<out TDlgIn, in TDlgOut>(TDlgIn input) 吗?当传递委托时,协变/逆变需要反过来。 - Stefan Steinegger
3个回答

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

此时,您将向 Func<Animal, string> 传递了一个 string


0
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作为一个参数(它将作为你的Func的结果被传递给“DoSomethingWithFunc”)。

网络上有很多关于这个问题的文章(什么是“covariantly valid”的意思),但我认为最好的解释是:https://blogs.msdn.microsoft.com/ericlippert/2009/12/03/exact-rules-for-variance-validity/

这意味着你当然可以把你的Func作为接口方法的结果。


此外,如果您想要反转类型以欺骗编译器(实际上只会更改输出类型,所以可能不会帮助您),您可以使用Func<Func<Tin,TOut>>。 - Jakub Szumiato
使用 Func<Func<>> 看起来很有趣,我会试一试。 - knittl

0

你试过这个吗?

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

当传递委托时,协变/逆变需要反过来。我不知道这是否有帮助。也不知道你在方法中想要做什么。


是的,这显然可以工作。但我希望我的输入是输入,而不是输入/输出交换。我不认为这会做我期望的事情。在我得出结论之前,我必须尝试一下。也许它确实做我需要的事情,但只是非常不直观。 - knittl
实际上,我不能将TDlgIn用作参数,因为它被标记为out(协变)。 - knittl
1
当你足够深入地思考它时,它实际上是直观的(除了整个事情超出任何直觉之外...)。您的类获得一个委托并通常希望调用它。因此,为了传递参数,它需要兼容。将参数传递给委托时,它正在离开您的类,因此是 out。从委托中返回的内容进入您的类,因此是 in - Stefan Steinegger
你说得对(这解释得很清楚)。也许我需要一个带有4个类型参数的委托 :) - 希望C#可以推断它们,否则使用该委托将变得非常麻烦。 - knittl
我仍然不知道你实际想要做什么。 - Stefan Steinegger
很难解释 :) 我有一个现有的 C,它以 Action<C> 作为参数(没有接口 → 没有变化)。我现在想通过引入流畅的构建器来简化该类的创建。这个构建器应该接受一个 Func<TIn, TOut> (类 C (有时)具有输入和输出字段/参数,我会将其映射到 TIn/TOut)。问题是,现有的类有点不太经过深思熟虑,但我目前无法重构它。 - knittl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接