为什么不搞砸<..>和行动统一

本文关键字:为什么不 | 更新日期: 2023-09-27 18:17:55

例如,我发现自己一直想通过一个带有返回且没有输入来代替ActionFunc

Func<int> DoSomething = ...;
Task.Run(DoSomething);

在哪里,我真的不在乎DoSomething的返回值.

但是,这些类型并不统一,我最终会包装通话

Task.Run(() => { DoSomething(); });

有没有办法在不包装的情况下统一这些类型?另外,它们不统一是否有很好的设计原因?

为什么不搞砸<..>和行动统一

您希望以下语句为真:

如果我有Func<T>,我应该能够在需要Action的地方使用它。

这将要求Func<T>(A(可分配给Action(B(可隐式转换为Action

如果我们假设 (A( 这将需要 T ,它可以是任何类型,可分配给 void .

Eric Lippert在他的博客中回答了这个问题:

为了从

方法组到委托类型的协变返回类型转换,难道不应该将"void"视为所有可能类型的超类型吗?

他的回答是"不",因为这最终与 CLI 规范不兼容。 CLI 规范要求返回值在堆栈上,因此void函数最终不会生成"pop"指令,而那些返回某些内容的函数会生成"pop"指令。 如果有某种方法可以有一个"动作",该"动作"可能包含一个void函数或一个返回某些东西的函数,这在编译时是未知的,编译器将不知道是否生成"pop"指令。

他接着说:

如果 CLI 规范说"任何函数的返回值都在'虚拟寄存器'中传回",而不是将其推送到堆栈上,那么我们可以使 void 返回委托与返回任何内容的函数兼容。您始终可以忽略寄存器中的值。但这不是 CLI 指定的,所以这不是我们可以做的。

换句话说,如果存在存储函数返回值的"虚拟寄存器"(在 CLI 规范中可能不存在(,C# 的编写者及其编译器本可以做你想要的,但他们不能,因为他们不能偏离 CLI 规范。

如果我们假设(B(,就会有一个重大变化,正如Eric Lippert在本博客中解释的那样。 将他博客中的示例改编为此,如果存在从Func<T>Action的隐式转换,则某些程序将不再像以前那样编译(重大更改(。 该程序当前可以编译,但请尝试取消注释隐式转换运算符,类似于您所要求的,它不会编译。

public class FutureAction
{
    public FutureAction(FutureAction action)
    {
    }
    //public static implicit operator FutureAction(Func<int> f)
    //{
    //    return new FutureAction(null);
    //}
    public static void OverloadedMethod(Func<FutureAction, FutureAction> a)
    {
    }
    public static void OverloadedMethod(Func<Func<int>, FutureAction> a)
    {
    }
    public static void UserCode()
    {
        OverloadedMethod(a => new FutureAction(a));
    }
}

(显然,这不是他们要做的,因为这仅适用于Func<int>而不是Func<T>,但它说明了问题。

总结

我认为您面临的问题是当时可能没有预见到 CLI 规范的工件,我猜他们不想引入重大更改以允许隐式转换使其正常工作。

从 CLI 标准中提取:

II.4.6.1 委托签名兼容性

委托只能可验证地绑定到目标方法,其中:

    目标方法的签名可委托分配给
  1. 委托的签名;

当且仅当以下所有条件都适用时,T 类型的目标方法或委托可委托分配给类型 D 的委托:

  1. 返回类型 U 的 T 和返回类型 V 的返回类型 V,V 可分配给 U