因此,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));