Почему не разрешается перегрузка С# между Func <T, T> и Action <T>?
Итак, довольно распространенный метод расширения для 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();
}
И теперь компилятор жалуется, что "вызов неоднозначен между следующими методами..."
Итак, мой вопрос: как может перегрузка Action метода Run вызвать неоднозначность, если она недопустима для группы методов?
Ответы
Ответ 1
Об этом уже объяснил Эрик и Джон в ответах на этот вопрос. Короче говоря - вот как работает компилятор С#; точно, когда речь идет о преобразовании группы методов, определяющем, какой делегат будет преобразован для использования разрешения перегрузки, который не принимает типы возврата в учетной записи:
Принцип здесь заключается в том, что определение конвертируемой группы объектов требует выбора метода из группы методов с использованием разрешения перегрузки, а разрешение перегрузки не учитывает типы возврата.
В вашем примере компилятор видит как Action<T>
, так и Func<T, T>
как наилучшее соответствие для Add
. Это добавляет до двух возможных вариантов, и поскольку для этого требуется одна соответствующая ошибка, выдается.
Ответ 2
попробуйте перегрузить правильно:
public static IEnumerable<TDest> Run<TSource, TDest>(this IEnumerable<TSource> source,
Func<TSource, TDest> action)
{
return source.Select(action).ToList();
}
Ответ 3
Я не могу ответить, почему, но для устранения двусмысленности вы можете явно использовать свою функцию:
invoice.Items.Run((Func<T,T>)db.InvoiceItems.Add);
или используйте lambda
invoice.Items.Run(x => db.InvoiceItems.Add(x));
Ответ 4
Я не знаю, почему он не может автоматически разрешить его, но вот два обходных пути:
// 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>
.