Подтвердить что ты не робот

Почему не разрешается перегрузка С# между 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 вызвать неоднозначность, если она недопустима для группы методов?

4b9b3361

Ответ 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>.