【问题标题】:Why doesn't C#'s overload resolution work between Func<T,T> and Action<T>? [duplicate]为什么 C# 的重载解析在 Func<T,T> 和 Action<T> 之间不起作用? [复制]
【发布时间】:2012-06-27 22:11:51
【问题描述】:

所以,IEnumerable 的一个相当常见的扩展方法,Run:

public static IEnumerable<T> Run<T>(this IEnumerable<T> source, Action<T> action)
{
    foreach (var item in source)
    {
        action(item);
        yield return item;
    }
}

当我尝试将它与 DbSet.Add 一起使用时:

invoice.Items.Run(db.InvoiceItems.Add);
// NB: Add method signature is
// public T Add(T item) { ... }

...编译器抱怨它有错误的返回类型,因为它需要一个 void 方法。因此,为 Run 添加一个使用 Func 而不是 Action 的重载:

public static IEnumerable<T> Run<T>(this IEnumerable<T> source, Func<T, T> action)
{
    return source.Select(action).ToList().AsEnumerable();
}

现在编译器抱怨“以下方法之间的调用不明确......”

所以我的问题是,Run方法的Action重载在对方法组无效的情况下怎么会导致歧义?

【问题讨论】:

  • db.InvoiceItems.Add的签名是什么?
  • 简短回答:x =&gt; x.ToString() 这个 lambda 应该简单地调用 ToString 还是调用 ToString 并返回其结果?换句话说,这个 lambda 应该作为一个函数还是一个动作来处理?编译器无法为您做出此决定,因此出现错误。
  • @Polity 但是这里没有 lambda。并且从方法组转换为委托永远不会将返回某物的方法更改为void-returning 委托。
  • @svick - Lambda 是 .NET 生态系统中的奇怪公民。只有它们取决于声明的类型(因此您不允许使用 var)。分配给操作的 lambda 根本不会返回。当重载决议必须在一个动作和一个函数之间进行选择时,它真的变成了一个丑陋的选择。现在我假设*编译器提前检测到这些问题并因此产生错误。
  • @Polity 我相信 Eric Lippert 对问题 roken 的回答很好地描述了这个问题。

标签: c# linq


【解决方案1】:

Eric 和 Jon 在回答 this question 时已经解释了这一点。长话短说——这就是 C# 编译器的工作原理;确切地说,在处理方法组转换时,决定将其转换为哪个委托时会利用重载决议,这不考虑返回类型

这里的原则是确定方法组可转换性需要使用重载决议从方法组中选择一个方法,而重载决议不考虑返回类型。

在您的示例中,编译器将Action&lt;T&gt;Func&lt;T, T&gt; 视为Add最佳匹配。这增加了两种可能的选择,因为它需要一个 - 发出适当的错误。

【讨论】:

    【解决方案2】:

    以正确的方式尝试重载:

    public static IEnumerable<TDest> Run<TSource, TDest>(this IEnumerable<TSource> source, 
        Func<TSource, TDest> action) 
    { 
     return source.Select(action).ToList(); 
    } 
    

    【讨论】:

    • 没有丝毫区别。
    • 这里应该去掉.ToList,避免执行查询
    • @SteveB 方法名称是 Run,但不是 LazyRun
    【解决方案3】:

    不知道为什么不能自动解决,但这里有两种解决方法:

    // with T replaced with the actual type:
    invoice.Items.Run((Func<T, T>)db.InvoiceItems.Add);
    invoice.Items.Run(new Func<T, T>(db.InvoiceItems.Add));
    

    为什么还需要这些方法?有什么问题:

    foreach (var item in invoice.Items)
        db.InvoiceItems.Add(item);
    

    这个的可读性要好得多。除非您有充分的理由需要 Run 方法,否则我建议您不要使用它。据我所知,没有这样的原因,至少对于Action&lt;T&gt; 过载而言。

    【讨论】:

    • Run 是一种常见的函数式声明式操作,我不同意 foreach 表单更具可读性。此外,一旦你把大括号放进去,它是四行而不是一行。我正在为六个子系列做这个;在每个之间添加一个空行,大约 30 行代码,这使得包含方法太长,所以我将每个 foreach 重构为一个单独的方法。然后我重构该方法以保持干燥,嘿,我有一个 Run 方法。
    • @MarkRendle 你的Run() 不是很实用。函数式编程的很大一部分是编写没有副作用的函数。 Run()用于副作用。我同意 Tim 的观点:foreach 更具可读性,使用 Run() 之类的方法并不是一个很好的做法。
    • @svick 我不同意。
    • @svick 有许多语言和/或框架,功能性的和其他的,它们具有类似于 Run 方法/功能的东西;想想 Ruby 和 Python 中的“each”方法,或者 Javascript 中的 array.forEach 方法,或者 Scheme 中的 for-each 标准库函数。
    • @MarkRendle 而且IEnumerable&lt;T&gt;上没有这种(扩展)方法也是有原因的。
    【解决方案4】:

    我无法回答为什么,但为了解决歧义,您可以明确地转换您的函数:

    invoice.Items.Run((Func<T,T>)db.InvoiceItems.Add); 
    

    或使用 lambda

    invoice.Items.Run(x => db.InvoiceItems.Add(x));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-22
      相关资源
      最近更新 更多