11

I'm trying to write a complement function, such that when provided with a function f, it returns a function which, when provided with the same input as f, returns it's logical opposite.

Having put similar code into VS2017, I get no errors, however I'm not yet able to run the code to see if it'll work as expected. My intention was to try this in a repl first, to see if it would do as expected. The code I used there was this:

   public static Func<T, bool> Complement<T>(Func<T, bool> f)
   {
       return (T x) => !f(x);
   }

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

   static public void Main(string[] args)
   {
     Func<int, bool> NotGreaterThanTwo = Complement(GreaterThanTwo);
     Console.WriteLine(NotGreaterThanTwo(1));
   }

Here is a link to the same.

Within the repl, I get the error:

main.cs(17,42): error CS0411: The type arguments for method `MainClass.Complement(System.Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly Compilation failed: 1 error(s), 0 warnings compiler exit status 1

I have looked at a few questions on stack overflow which cover the same error message, for instance this and this, but I'm not able to see how they relate to this issue I'm having.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
OliverRadini
  • 6,238
  • 1
  • 21
  • 46
  • 1
    The typesystem is unable to determine what is when you call it in your main method, you can 'help' it by specifying the input type to Complement e.g. change `Complement(GreaterThanTwo);` to `Complement(GreaterThanTwo); ` – MikNiller Jun 20 '19 at 08:02
  • 1
    @OliverRadini the links explain exactly what's wrong and should be considered duplicates. The compiler can't infer which type to use for `Complement`. – Panagiotis Kanavos Jun 20 '19 at 08:02
  • @PanagiotisKanavos The links perhaps explain exactly what's wrong but I'm afraid they didn't to me. Considering that, you may want to still mark this as duplicate, that's up to you of course, though I believe there's room on stack overflow for questions which, when worded differently, offer an insight which may not be present in existing questions, despite the fact that they may be considered by some to have the same content by some. – OliverRadini Jun 20 '19 at 08:05
  • @PanagiotisKanavos further, for your information, the correct response to a duplicate is to mark the question as such, I fail to see the point in adding a comment simply saying that you believe the question is a duplicate, when the ability to have it properly considered as such is available to you – OliverRadini Jun 20 '19 at 08:06
  • 1
    @OliverRadini because I'm trying to find Eric Lippert's video with the explanation from that 2006 answer. It's missing after MSDN's blog engine change. In any case, it seems that the actual answer is that `Complement(GreaterThanTwo)` is trying to use a method group, not a `Func`, which is what confuses the compiler – Panagiotis Kanavos Jun 20 '19 at 08:08
  • 1
    If we privide explicit `Func` e.g. `Func gt = GreaterThanTwo; var NotGreaterThanTwo = Complement(gt);` the code compiles – Dmitry Bychenko Jun 20 '19 at 08:13
  • @DmitryBychenko - as was already noted in the 1st comment here. – H H Jun 20 '19 at 08:14
  • 1
    Typecasting also works: `Complement((Func)GreaterThanTwo)` – H H Jun 20 '19 at 08:14
  • 1
    I'm curious about "similar code into VS2017, I get no errors". Can you post the compiler and framework versions for that? (no repro). – H H Jun 20 '19 at 08:17
  • I just went back to C#4 and get the same error. – H H Jun 20 '19 at 08:22
  • @HenkHolterman The conversion rules changed [in VS 2010](https://learn.microsoft.com/en-us/previous-versions/ee855831(v=vs.110)) – Panagiotis Kanavos Jun 20 '19 at 08:46

2 Answers2

10

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>.

Community
  • 1
  • 1
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • 3
    We started commenting at the same time, looked through the same docs and posted basically the same answer around the same time :) – Camilo Terevinto Jun 20 '19 at 08:43
  • Many thanks for the comprehensive answer, which makes it clear what the problem is here – OliverRadini Jun 20 '19 at 08:45
  • 1
    @OliverRadini frankly, the definite answer would be a link to a specific rule or explanation from Jon Skeet or Eric Lippert. I'm not a language lawyer so I'm not sure about the deeper reasons. I don't even think about method groups conciously until I hit compilation errors like this one – Panagiotis Kanavos Jun 20 '19 at 08:49
  • 1
    @CamiloTerevinto guess we've been bitten by that error often enough that we recognize it's not that simple... – Panagiotis Kanavos Jun 20 '19 at 08:50
9

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);
    
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120