Complement higher order function

Complement(GreaterThanTwo) is trying to use a method group, not a Func<int,bool> delegate. This fails because Complement<T> expects a generic delegate.

The call would compile with a Func<int,bool>, eg :

Func<int,bool> cmp= x=>x > 2;
var NotGreaterThanTwo = Complement(cmp);

There's an implicit conversion from method groups to delegates which means this works too :

Func<int,bool> cmp= GreaterThanTwo;
var NotGreaterThanTwo = Complement(cmp);

Which raises the question why didn't the original code work? An explicit cast also works:

var NotGreaterThanTwo = Complement((Func<int,bool>)GreaterThanTwo);

A method group represents a group of overloaded methods, not just a single method. This means that the compiler has to be able to find which of the available groups to use in any situation.

The rest is supposition as I haven't found a definite reference or design note about this specific case.

The first two method group conversion rules probably explains what's wrong :

  • A single method M is selected corresponding to a method invocation (Method invocations) of the form E(A), with the following modifications:

    • The argument list A is a list of expressions, each classified as a variable and with the type and modifier (ref or out) of the corresponding parameter in the formal_parameter_list of D.
    • The candidate methods considered are only those methods that are applicable in their normal form (Applicable function member), not those applicable only in their expanded form.
  • If the algorithm of Method invocations produces an error, then a compile-time error occurs. Otherwise the algorithm produces a single best method M having the same number of parameters as D and the conversion is considered to exist.

In Complement<T>(Func<T, bool> f) there's no invocation, so the compiler doesn't know which method in the group to pick and convert. It doesn't even know what T is, so it can't know if any of the methods in that group match.

On the other hand this works :

var xx=new []{1,2,3}.Where(GreaterThanTwo);

In this case though, Where's signature is :

public static System.Collections.Generic.IEnumerable<TSource> Where<TSource> (
    this System.Collections.Generic.IEnumerable<TSource> source, 
    Func<TSource,bool> predicate);

and the type argument is already available from IEnumerable<TSource>.


From What is a method group in C#?

A method group is the name for a set of methods (that might be just one) - i.e. in theory the ToString method may have multiple overloads (plus any extension methods): ToString(), ToString(string format), etc - hence ToString by itself is a "method group".

When you use:

Func<int, bool> NotGreaterThanTwo = Complement(GreaterThanTwo);

GreaterThanTwo is a method group. So, this could be true:

public static bool GreaterThanTwo (int x) {
  return x > 2;
}

// to make it clear this is something completely different
public static bool GreaterThanTwo (Action<bool, string, object> x) {
  return false;
}

So, the compiler cannot infer which specific method you are referring to, and you need to help it by resolving that ambiguity.

How you decide to solve that it's up to you, but there are at least 3 options here:

  1. Specify the generic argument:

    Complement<int>(GreaterThanTwo);
    
  2. Implicitly convert the method group to the delegate you want:

    Func<int, bool> greaterThanTwo = GreaterThanTwo; 
    var notGreaterThanTwo = Complement(greaterThanTwo);
    
  3. Explicitly convert the method group to the delegate you want:

    Complement((Func<int, bool>)GreaterThanTwo);
    

Tags:

C#