空条件运算符不适用于泛型方法中的 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&lt;T&gt; 但按预期工作。

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&lt;int&gt;

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-conditionalnull-coalescing 运算符的值类型的 Func&lt;T&gt; 应该可以工作。

但是,一旦null-conditional 的左侧泛型类型是没有约束的泛型,那么它就不能应用它应该能够考虑的相同逻辑,它可以将相同的逻辑应用于两个值类型 和 明确应用类型时的引用类型。

我知道编译器的限制,但它对我来说没有意义,为什么它不允许它以及为什么它希望输出不同,无论它是参考类型还是值类型,考虑到手动应用类型将产生预期的结果。

【问题讨论】:

var x = func?.Invoke() 也会失败。 x 可以为 null 或具有某些值。编译器不知道这一点。除此之外,编译器不知道T 是否是引用类型。请注意,null 对值类型无效。例如你不能写int I = null。因此你得到的错误。 简而言之,如果T 是引用类型,则Func&lt;T&gt;?.Invoke() 的类型必须是T,如果T 是值类型,则T? 必须是T?。由于 .NET 中的泛型只有一种实现方式(与 C++ 中的模板相反),因此很难做到这一点。从理论上讲,编译器可以通过巧妙的代码生成向后弯腰来完成这项工作。在实践中,C# 编译器的理念不是向后弯腰,而是不允许那些不能直接完成的事情。 【参考方案1】:

不幸的是,我相信您遇到了编译器的边缘情况。 ?. 运算符需要为类返回default(RetrunTypeOfRHS),为结构返回default(Nullable&lt;RetrunTypeOfRHS&gt;)。因为您没有将 T 限制为类或结构,所以它无法确定要提升到哪一个。

Action&lt;T&gt; 起作用的原因是因为这两种情况下右侧的返回类型都是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&lt;T&gt; 是一个引用类型的委托。如果对T 没有任何约束,代码将无法编译。 Error CS0023 Operator '?' cannot be applied to operand of type 'T'。当你添加约束where T: structwhere 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&lt;T&gt; 一个引用类型,如果FuncFunc&lt;int&gt;类型(结果是int?类型),func?.Invoke()是合法的。问题是特别在原始代码中,涉及到一个泛型类型T,它可以是引用类型,也可以是值类型。 @JeroenMostert 我添加了一些信息。感谢您的评论。 为类生成的 IL 没有意义。问题似乎是因为编译器生成了该 IL。查看生成的 IL,逻辑如下:如果 func.Invokedefault(T) 的结果为空,则将值设置为 default(T)。据我所知,没有办法控制类的default(T) 的值,这意味着它们将总是设置为空。基本上它说 如果结果为 null 然后将其设置为 null 这是没有意义的。如果您问我,它应该生成与结构相同的代码,这似乎合乎逻辑。 这就是为什么我写了`但是:??当 T 是结构时,运算符没有意义。我认为编译器应该对此给出编译错误。因为使用??不允许在结构上。`

以上是关于空条件运算符不适用于泛型方法中的 Func<T>的主要内容,如果未能解决你的问题,请参考以下文章

为啥条件渲染不适用于 vuejs 中的表单输入

为啥三元条件不适用于字符串连接

动画不适用于带有tailwindcss的className中的条件

如何使用条件三元运算符在 lambda 之间有条件地分配 Func<>?

inheritFromWidgetOfExactType 不适用于通用 InheritedWidget

iOS + Swift 2:UITableViewCell 中的动态高度不适用于空视图