我发现自己经常想要传递Func
并返回而没有输入代替Action
,例如
Func<int> DoSomething = ...;
Task.Run(DoSomething);
其中,我并非真正关心DoSomething
的返回值。
然而,这些类型并不统一,我最终还是打包了
Task.Run(() => { DoSomething(); });
有没有办法让这些类型在没有包装的情况下统一?此外,还有很好的设计理由说明他们为什么不统一?
答案 0 :(得分:6)
您希望以下陈述成立:
如果我有
Func<T>
,我应该可以在需要Action
的地方使用它。
这需要将Func<T>
(A)分配给Action
或(B)隐式转换为Action
。
如果我们假设(A)需要T
,可以是任何类型,可分配给void
。
Eric Lippert在his blog中回答了这个问题:
为了从方法组到委托类型的协变返回类型转换的目的,不应该将“void”视为所有可能类型的超类型吗?
他的回答是“不”,因为这最终与CLI规范不兼容。 CLI规范要求返回值在堆栈上,因此void
函数不会最终生成“pop”指令,而那些返回某些内容的指令会生成“pop”指令。如果有某种方法可以包含一个“动作”,它可以包含一个void
函数或一个返回某些东西的函数,这在编译时是未知的,编译器就不会知道是否生成“流行”指令。
他接着说:
如果CLI规范说“任何函数的返回值都传回'虚拟寄存器'”而不是将其压入堆栈,那么我们就可以使返回的void委托与返回任何东西的函数兼容。您始终可以忽略寄存器中的值。但这不是CLI指定的,所以这不是我们能做的。
换句话说,如果存在这个“虚拟寄存器”,其中存储了函数的返回值(可能在CLI规范中不存在),C#的编写者及其编译器可能已经完成了你想要的,但是他们不能,因为他们不能脱离CLI规范。
如果我们假设(B),就会有一个突破性的变化,正如Eric Lippert在this blog中解释的那样。如果有从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规范的一个工件,当时可能并不是很好,而且我猜他们不想引入重大修改以允许隐式转换为它工作
答案 1 :(得分:1)
II.4.6.1委托签名兼容性
代表只能以可验证的方式绑定目标方法:
- 目标方法的签名是委托 - 可分配 - 委托的签名;
醇>...
类型D的目标方法或委托是委托 - 可分配 - 类型D的委托,当且仅当以下所有情况适用时:
- T的返回类型U和D的返回类型V,V是 assignable-to U。
醇>