为什么 Func<...> 和 Action 不统一?
Why don't Func<...> and Action unify?
我发现自己一直想传递一个 Func
和一个 return 并且没有任何输入来代替 Action
,例如
Func<int> DoSomething = ...;
Task.Run(DoSomething);
其中,我并不真正关心 DoSomething
的 return 值。
然而,这些类型并不统一,我最终结束了调用
Task.Run(() => { DoSomething(); });
有没有办法在不换行的情况下统一这些类型?另外,它们不统一是否有好的设计原因?
从 CLI Standard 拉取:
II.4.6.1 Delegate signature compatibility
Delegates can only be verifiably bound to target methods where:
- the signatures of the target method is delegate-assignable-to the signature of the delegate;
...
A target method or delegate of type T is delegate-assignable-to a delegate of type D if and only if all of the following apply:
- The return type U of T and return type V of D, V is assignable-to U.
您希望以下陈述为真:
If I have a Func<T>
, I should be able to use it where an Action
is required.
这将要求 Func<T>
可以 (A) 隐式分配给 Action
或 (B)可转换为 Action
。
如果我们假设 (A) 需要 T
,它可以是任何类型,可分配给 void
.
Eric Lippert 在 his blog 中回答了这个问题:
Shouldn’t “void” be considered a supertype of all possible types for the purposes of covariant return type conversions from method groups to delegate types?
他的回答是 "No," 因为这最终与 CLI 规范不兼容。 CLI 规范要求 return 值进入堆栈,因此 void
函数不会最终生成 "pop" 指令,而那些执行 return 操作的函数会生成"pop" 指令。如果有某种方法可以使 "action" 可以包含 void
函数或 returned 某些东西的函数,这在编译时是未知的,编译器就不会知道是否生成"pop"指令。
他接着说:
Had the CLI specification said “the returned value of any function is passed back in a ‘virtual register’” rather than having it pushed onto the stack, then we could have made void-returning delegates compatible with functions that returned anything. You can always just ignore the value in the register. But that’s not what the CLI specified, so that’s not what we can do.
换句话说,如果有这个 "virtual register" 函数的 return 值被存储(在 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 规范的产物,当时可能没有预见到,我猜他们不想引入重大更改以允许对其进行隐式转换只是工作。
我发现自己一直想传递一个 Func
和一个 return 并且没有任何输入来代替 Action
,例如
Func<int> DoSomething = ...;
Task.Run(DoSomething);
其中,我并不真正关心 DoSomething
的 return 值。
然而,这些类型并不统一,我最终结束了调用
Task.Run(() => { DoSomething(); });
有没有办法在不换行的情况下统一这些类型?另外,它们不统一是否有好的设计原因?
从 CLI Standard 拉取:
II.4.6.1 Delegate signature compatibility
Delegates can only be verifiably bound to target methods where:
- the signatures of the target method is delegate-assignable-to the signature of the delegate;
...
A target method or delegate of type T is delegate-assignable-to a delegate of type D if and only if all of the following apply:
- The return type U of T and return type V of D, V is assignable-to U.
您希望以下陈述为真:
If I have a
Func<T>
, I should be able to use it where anAction
is required.
这将要求 Func<T>
可以 (A) 隐式分配给 Action
或 (B)可转换为 Action
。
如果我们假设 (A) 需要 T
,它可以是任何类型,可分配给 void
.
Eric Lippert 在 his blog 中回答了这个问题:
Shouldn’t “void” be considered a supertype of all possible types for the purposes of covariant return type conversions from method groups to delegate types?
他的回答是 "No," 因为这最终与 CLI 规范不兼容。 CLI 规范要求 return 值进入堆栈,因此 void
函数不会最终生成 "pop" 指令,而那些执行 return 操作的函数会生成"pop" 指令。如果有某种方法可以使 "action" 可以包含 void
函数或 returned 某些东西的函数,这在编译时是未知的,编译器就不会知道是否生成"pop"指令。
他接着说:
Had the CLI specification said “the returned value of any function is passed back in a ‘virtual register’” rather than having it pushed onto the stack, then we could have made void-returning delegates compatible with functions that returned anything. You can always just ignore the value in the register. But that’s not what the CLI specified, so that’s not what we can do.
换句话说,如果有这个 "virtual register" 函数的 return 值被存储(在 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 规范的产物,当时可能没有预见到,我猜他们不想引入重大更改以允许对其进行隐式转换只是工作。