12

So, a fairly common extension method for 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;
    }
}

When I try to use that with, for instance, DbSet.Add:

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

... the compiler complains that it has the wrong return type, because it is expecting a void method. So, add an overload for Run that takes a Func instead of Action:

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

And now the compiler complains that "The call is ambiguous between the following methods..."

So my question is, how can the Action overload of the Run method cause ambiguity when it is not valid for the method group?

Mark Rendle
  • 9,274
  • 1
  • 32
  • 58
  • What is the signature of `db.InvoiceItems.Add`? – leppie Jun 27 '12 at 11:27
  • Short answer: `x => x.ToString()` should this lambda simply invoke ToString or invoke ToString and return its result? In other words, should this lambda be handled as a func or an action? The compiler can't make this decision for you so hence an error. – Polity Jun 27 '12 at 11:57
  • @Polity But there is no lambda here. And converting from a method group to a delegate can never change a method that returns to something into a `void`-returning delegate. – svick Jun 27 '12 at 12:06
  • @svick - Lambdas are strange citizens in the .NET ecosystem. Only they depend on the declared type (hence your not allowed to use var). A lambda assigned to an action simply doesn't return. When overload resolution has to choose between an action and a func, it becomes really an ugly choice. Now i assume* that the compiler detects these problems in advance and therefore generates the error. – Polity Jun 27 '12 at 12:10
  • 1
    @Polity I believe Eric Lippert's answer to the question roken linked to describes this issue very well. – svick Jun 27 '12 at 12:15

4 Answers4

5

This has already been explained by Eric and Jon in answers to this question. Long story short - this is how C# compiler works; precisely, when dealing with method group conversion deciding what delegate it will be converted to utilizes overload resolution, which does not take return types in account:

The principle here is that determining method group convertibility requires selecting a method from a method group using overload resolution, and overload resolution does not consider return types.

In your example compiler sees both Action<T> and Func<T, T> as best match for Add. This adds up to two possible choices, and since it requires one - appropriate error is issued.

Community
  • 1
  • 1
k.m
  • 30,794
  • 10
  • 62
  • 86
0

try overload in right way:

public static IEnumerable<TDest> Run<TSource, TDest>(this IEnumerable<TSource> source, 
    Func<TSource, TDest> action) 
{ 
 return source.Select(action).ToList(); 
} 
Serj-Tm
  • 16,581
  • 4
  • 54
  • 61
0

I can't answer why but to resolve the ambiguity you can explicitly cast your function:

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

or use a lambda

invoice.Items.Run(x => db.InvoiceItems.Add(x));
roken
  • 3,946
  • 1
  • 19
  • 32
0

I don't know why it can't automatically resolve it, but here are two workarounds:

// 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));

Why do you need these methods anyway? What's wrong with:

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

The readability of this one is much better. Unless you have a good reason for needing the Run method, I'd recommend against using it. From what I've seen, there's not such a reason, at least for the Action<T> overload.

Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • Run is a common functional-style declarative operation, and I disagree that the foreach form is more readable. Moreover, once you've put the braces in, it's four lines instead of one. I'm doing this for half a dozen child collections; add in a blank line between each and that's ~30 lines of code, which makes the containing method too long, so I refactor each foreach out into a separate method. Then I refactor that method to keep things DRY and, hey presto, I've got a Run method anyway. – Mark Rendle Jun 27 '12 at 12:09
  • 1
    @MarkRendle Your `Run()` is not very functional. Big part of functional programming is writing functions that don't have side-effects. And `Run()` is useful *only* for side-effects. I agree with Tim on this: `foreach` is more readable and using methods like `Run()` is not a very good practice. – svick Jun 27 '12 at 14:19
  • @svick I respectfully disagree. – Mark Rendle Jun 27 '12 at 22:15
  • @svick There are many languages and/or frameworks, functional and otherwise, which have something like the Run method/function; think of the "each" methods in Ruby and Python, or the array.forEach method in Javascript, or the for-each standard library function in Scheme. – Mark Rendle Jun 27 '12 at 22:24
  • @MarkRendle And there is also a reason why there is no such (extension) method on `IEnumerable`. – svick Jun 27 '12 at 22:54