为什么不搞砸<..>和行动统一
本文关键字:为什么不 | 更新日期: 2023-09-27 18:17:55
例如,我发现自己一直想通过一个带有返回且没有输入来代替Action
的Func
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 委托签名兼容性
委托只能可验证地绑定到目标方法,其中:
目标方法的签名可委托分配给
- 委托的签名;
。
当且仅当以下所有条件都适用时,T 类型的目标方法或委托可委托分配给类型 D 的委托:
- 返回类型 U 的 T 和返回类型 V 的返回类型 V,V 可分配给 U。