为什么 C# 的重载解析在 Func<T、T> 和 Action 之间不起作用<T>?



因此,IEnumerable的一个相当常见的扩展方法,运行:

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 方法。因此,为运行添加一个重载,该重载采用 Func 而不是操作:

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

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

所以我的问题是,当 Run 方法的操作重载对方法组无效时,它如何导致歧义?

埃里克和乔恩在回答这个问题时已经解释过这一点。长话短说 - 这就是 C# 编译器的工作方式;确切地说,在处理方法组转换时,决定将其转换为哪个委托时,会利用重载解析,该解析不考虑返回类型

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

在您的示例中,编译器将Action<T>Func<T, T>视为Add的最佳匹配。这加起来有两个可能的选择,并且由于它需要一个 - 发出适当的错误。

尝试以正确的方式重载:

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

为什么它不能自动解决它,但这里有两个解决方法:

// 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<T>超载而言。

我无法回答为什么,但要解决歧义,您可以显式转换您的函数:

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

或使用 λ

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

最新更新