空条件运算符不适用于泛型方法中的 Func<T>
null-conditional operator doesn't work with Func<T> inside a generic method
这是一个编译器错误还是有一个特定的选择原因导致 null 条件运算符不能在泛型方法中使用 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):
经过更多的研究,它变得更没有意义,即使有以下内容:
假设我们有一个 class(参考类型)
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
的左侧泛型类型是没有约束的泛型,那么它就不能应用它应该能够考虑的相同逻辑它可以对两者应用相同的逻辑值类型 和 引用类型,当类型被显式应用时。
我知道编译器的限制,只是我不明白为什么它不允许它以及为什么它希望输出不同,无论是参考还是值类型考虑手动应用类型将产生预期的结果。
您应该对泛型函数设置约束:
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
是 class 时生成的代码不同。所以我们修复了 ?
错误。但是:当 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'
不幸的是,我认为您遇到了编译器的边缘情况。 ?.
运算符需要 return default(RetrunTypeOfRHS)
用于 类 和 default(Nullable<RetrunTypeOfRHS>)
用于结构。因为您没有将 T
约束为 类 或结构,所以它无法判断要提升到哪个。
Action<T>
起作用的原因是因为右侧的 return 类型对于这两种情况都是 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.
}
这是一个编译器错误还是有一个特定的选择原因导致 null 条件运算符不能在泛型方法中使用 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):
经过更多的研究,它变得更没有意义,即使有以下内容:
假设我们有一个 class(参考类型)
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
的左侧泛型类型是没有约束的泛型,那么它就不能应用它应该能够考虑的相同逻辑它可以对两者应用相同的逻辑值类型 和 引用类型,当类型被显式应用时。
我知道编译器的限制,只是我不明白为什么它不允许它以及为什么它希望输出不同,无论是参考还是值类型考虑手动应用类型将产生预期的结果。
您应该对泛型函数设置约束:
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
是 class 时生成的代码不同。所以我们修复了 ?
错误。但是:当 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'
不幸的是,我认为您遇到了编译器的边缘情况。 ?.
运算符需要 return default(RetrunTypeOfRHS)
用于 类 和 default(Nullable<RetrunTypeOfRHS>)
用于结构。因为您没有将 T
约束为 类 或结构,所以它无法判断要提升到哪个。
Action<T>
起作用的原因是因为右侧的 return 类型对于这两种情况都是 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.
}