空条件运算符不适用于泛型方法中的 Func<T>
Posted
技术标签:
【中文标题】空条件运算符不适用于泛型方法中的 Func<T>【英文标题】:null-conditional operator doesn't work with Func<T> inside a generic method 【发布时间】:2017-05-28 23:16:08 【问题描述】:这是一个编译器错误,还是有特定选择的原因导致空条件运算符不能与泛型方法中的Func
一起使用?
举个例子,下面不编译
public static T Test<T>(Func<T> func)
return func?.Invoke() ?? default(T);
编译器产生的错误是CS0023 Operator '?' cannot be applied to operand of type 'T'
我知道你也可以这样做:
public static T Test<T>(Func<T> func)
return func != null ? func() : default(T);
那为什么不允许呢?
进一步详细说明Action<T>
但按预期工作。
public static void Test<T>(Action<T> action, T arg)
action?.Invoke(arg);
更新(2017-01-17):
经过更多的研究,它变得更没有意义了,即使有以下几点:
假设我们有一个类(引用类型)
public class Foo
public int Bar get; set;
假设我们有一个Func<int>
Func<int> fun = () => 10;
以下作品:
// This work
var nullableBar = foo?.Bar; // type of nullableBar is int?
var bar = nullableBar ?? default(int); // type of bar is int
// And this work
nullableBar = fun?.Invoke(); // ditto
bar = nullableBar ?? default(int); // ditto
这意味着根据那里应用的逻辑,使用 null-conditional
和 null-coalescing
运算符的值类型的 Func<T>
应该可以工作。
但是,一旦null-conditional
的左侧泛型类型是没有约束的泛型,那么它就不能应用它应该能够考虑的相同逻辑,它可以将相同的逻辑应用于两个值类型 和 明确应用类型时的引用类型。
我知道编译器的限制,但它对我来说没有意义,为什么它不允许它以及为什么它希望输出不同,无论它是参考类型还是值类型,考虑到手动应用类型将产生预期的结果。
【问题讨论】:
var x = func?.Invoke()
也会失败。 x
可以为 null 或具有某些值。编译器不知道这一点。除此之外,编译器不知道T
是否是引用类型。请注意,null
对值类型无效。例如你不能写int I = null
。因此你得到的错误。
简而言之,如果T
是引用类型,则Func<T>?.Invoke()
的类型必须是T
,如果T
是值类型,则T?
必须是T?
。由于 .NET 中的泛型只有一种实现方式(与 C++ 中的模板相反),因此很难做到这一点。从理论上讲,编译器可以通过巧妙的代码生成向后弯腰来完成这项工作。在实践中,C# 编译器的理念不是向后弯腰,而是不允许那些不能直接完成的事情。
【参考方案1】:
不幸的是,我相信您遇到了编译器的边缘情况。 ?.
运算符需要为类返回default(RetrunTypeOfRHS)
,为结构返回default(Nullable<RetrunTypeOfRHS>)
。因为您没有将 T
限制为类或结构,所以它无法确定要提升到哪一个。
Action<T>
起作用的原因是因为这两种情况下右侧的返回类型都是void
,所以它不需要决定要执行哪个提升。
您将需要使用您显示的长格式或在T
上使用两种具有不同约束的方法
public static T TestStruct<T>(Func<T> func) where T : struct
return func?.Invoke() ?? default(T);
public static T TestClass<T>(Func<T> func) where T : class
return func?.Invoke(); // ?? default(T); -- This part is unnecessary, ?. already
// returns default(T) for classes.
【讨论】:
default(T);
当 T 是 class 时始终为 null,因此可以安全地删除它。 resharper 总是提醒我 xD
@M.kazemAkhgary 刷新,太慢了:)
编译器错误非常可怕。找出问题所在根本无济于事。 +1
接受这个作为答案,因为它解释了操作员的功能。如果你问我,这只是一个奇怪的行为。无论是引用还是值类型,它都应该将值视为相同的值,尤其是如果您定义了默认值。
@Bauss:它不能将值视为相同,这就是重点。不能为子表达式 func?.Invoke()
分配单一类型。实际上,该语言必须重新定义可空性的概念才能使其发挥作用。对于可以以另一种避免问题的方式轻松编写的东西来说,这有点多。 (在您的替代表达式中,没有打字问题:func()
始终是 T
类型,default(T)
也是。)【参考方案2】:
你应该对泛型函数设置一个约束:
public static T Test<T>(Func<T> func) where T: class
return func?.Invoke() ?? default(T);
因为结构不能为空,而?.
需要引用类型。
由于 Jeroen Mostert 的评论,我了解了幕后发生的事情。 Func<T>
是一个引用类型的委托。如果对T
没有任何约束,代码将无法编译。 Error CS0023 Operator '?' cannot be applied to operand of type 'T'
。当你添加约束where T: struct
或where T: class
时,就会产生底层代码。
代码编写:
public static T TestStruct<T>(Func<T> func) where T : struct
return func?.Invoke() ?? default(T);
public static T TestClass<T>(Func<T> func) where T : class
return func?.Invoke() ?? default(T);
使用 ILSpy 生成和反编译的代码:
public static T TestStruct<T>(Func<T> func) where T : struct
return (func != null) ? func.Invoke() : default(T);
public static T TestClass<T>(Func<T> func) where T : class
T arg_27_0;
if ((arg_27_0 = ((func != null) ? func.Invoke() : default(T))) == null)
arg_27_0 = default(T);
return arg_27_0;
如您所见,T
是结构时生成的代码与 T
是类时生成的代码不同。所以我们修复了?
错误。但是:当T
是结构时,??
运算符没有意义。 我认为编译器应该对此给出编译错误。因为不允许在结构上使用??
。 #BeMoreStrict
例如:
如果我写:
var g = new MyStruct();
var p = g ?? default(MyStruct);
我得到编译错误:
Error CS0019 Operator '??' cannot be applied to operands of type 'MainPage.MyStruct' and 'MainPage.MyStruct'
【讨论】:
default(T);
始终为 null,因此可以安全删除。
“?.
需要引用类型”充其量是误导,最坏的情况是不正确。 Func<T>
是一个引用类型,如果Func
是Func<int>
类型(结果是int?
类型),func?.Invoke()
是合法的。问题是特别在原始代码中,涉及到一个泛型类型T
,它可以是引用类型,也可以是值类型。
@JeroenMostert 我添加了一些信息。感谢您的评论。
为类生成的 IL 没有意义。问题似乎是因为编译器生成了该 IL。查看生成的 IL,逻辑如下:如果 func.Invoke
或 default(T)
的结果为空,则将值设置为 default(T)
。据我所知,没有办法控制类的default(T)
的值,这意味着它们将总是设置为空。基本上它说 如果结果为 null 然后将其设置为 null 这是没有意义的。如果您问我,它应该生成与结构相同的代码,这似乎合乎逻辑。
这就是为什么我写了`但是:??当 T 是结构时,运算符没有意义。我认为编译器应该对此给出编译错误。因为使用??不允许在结构上。`以上是关于空条件运算符不适用于泛型方法中的 Func<T>的主要内容,如果未能解决你的问题,请参考以下文章
动画不适用于带有tailwindcss的className中的条件
如何使用条件三元运算符在 lambda 之间有条件地分配 Func<>?