Func 上的 C# 可选参数丢失泛型类型推断

C# optional parameter on func losing generic type inference

我有简单的通用 enum 解析方法:

public static T Parse<T>(string value) => (T)Parse(typeof(T), value, false);

用法是:

    public IEnumerable<DocumentTypesEnum> FileTypeEnums => 
        FileTypes.Split(',').Select(Extensions.StringEnumerator.Parse<DocumentTypesEnum>);

以上编译正常。

将可选参数添加到 Parse<T>:

public static T Parse<T>(string value, bool ignoreCase = false) => 
        (T)Parse(typeof(T), value, ignoreCase);

现在编译器不开心!

它工作得很好,显式提供 argument/type(这就是错误消息所说的内容:)):

public IEnumerable<DocumentTypesEnum> FileTypeEnums => 
            FileTypes.Split(',').Select(f => Extensions.StringEnumerator.Parse<DocumentTypesEnum>(f));

但我很好奇为什么编译器只是通过向 func 选择器添加可选参数来抱怨?

注意: 我能找到的最接近的讨论是 Unable to infer generic type with optional parameters,但它更多的是 命名参数 问题而不是可选参数John Skeet 说。

这是因为即使您有可选参数,编译器解析也不关心 - 参数签名必须与 Select() 的调用相匹配。

Select的输入参数大致为Func<TInput, TOutput>TInputstringTOutputDocumentTypesEnum。在这种情况下,没有其他参数的余地。

当您进行显式调用时,您实际上创建了一个具有可接受签名的新 delegate,然后它使用 hidden/optional 参数调用您的 "non acceptable" 签名。

让我们对此进行简化,以从图片中删除类型推断和重载解析。您想要的是一个 方法组转换 ,它考虑了可选参数 - 而这不是 C# 语言规则的一部分。

这是一个更简单的具体示例:

using System;

class Program
{
    static void Foo(int x = 1)
    {
        Console.WriteLine(x);
    }

    static void Main()
    {
        Action action = Foo; // Error
        action();
    }
}

编译失败,因为没有无参数的 Foo 方法(具有 void return 类型)。

语言规范 可以 编写,以便编译器生成一个方法,该方法仅使用可选参数调用 Foo,然后创建一个引用的委托那个新方法,但它不是那样写的。我相信目前,当您从方法组转换创建委托时,该委托的调用列表始终是相应的方法本身,而不是某种 "proxy" 方法。

C# 5 ECMA 标准的相关部分是第 11.8 节,其中包含:

The candidate methods considered are only those methods that are applicable in their normal form and do not omit any optional parameters (§12.6.4.2). Thus, candidate methods are ignored if they are applicable only in their expanded form, or if one or more of their optional parameters do not have a corresponding parameter in D.

玛拉基的回答解释了为什么会这样。

如果你想要一种干净利落的解决方法,答案是只有两个 Parse 方法 - 一个有两个参数,一个有一个,并让后者调用前者。

public static T Parse<T>(string value, bool ignoreCase) 
    => (T)Parse(typeof(T), value, ignoreCase);

public static T Parse<T>(string value) => Parse<T>(value, false);