Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions proposals/type-inference-from-method-group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Type inference using method group natural type

Champion issue: https://github.com/dotnet/csharplang/issues/9007

## Summary
[summary]: #summary

It allows the natural type of a method group to contribute to method type inference

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
It allows the natural type of a method group to contribute to method type inference
It allows a method group with a unique signature to contribute to method type inference.

Because as written I think the spec would still work even if method groups had no natural type.


The current [type inference rules](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1263-type-inference) allow
- method groups to contribute to output type inference
- infering bounds for the `Ui` type parameters in `C<U1 ... Uk>` when given a `C<V1...Vk>` and `C` is a class, struct, interface or delegate type.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- infering bounds for the `Ui` type parameters in `C<U1 ... Uk>` when given a `C<V1...Vk>` and `C` is a class, struct, interface or delegate type.
- inferring bounds for the `Ui` type parameters in `C<U1 ... Uk>` when given a `C<V1...Vk>` and `C` is a class, struct, interface or delegate type.

But they don't allow method groups to contribute to bounds.

```
Test(IsEven); // Error CS0411 The type arguments for method 'Program.Test<T>(Func<T, bool>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

partial class Program
{
public static bool IsEven(int x) => x % 2 == 0;
public static void Test<T>(Func<T, bool> predicate) { }
}
```

## Design

We modify the [explicit parameter type inference rules](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12638-explicit-parameter-type-inferences) to not just apply to explicitly-typed lambdas, but also to method groups:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps expand on potential breaking changes that will come from this? IIRC that was a major reason we didn't do this in the first place.

@jnm2 jnm2 Jun 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g. the exact inference causes:

static bool P(object o) => true;
static void M<T>(Func<T, bool> f, T value) => Console.WriteLine(typeof(T));

M(P, "hello"); // Today, prints System.String; after proposal, prints System.Object
M(bool (object o) => true, "hello"); // Prints System.Object

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, this would stop compiling:

static bool P(object o) => true;
static void M<T>(Func<T, bool> f, Func<T, bool> g) { }

M((string s) => true, P); // Compiles now, would behave like the next line after the proposal
M((string s) => true, bool (object o) => true); // Fails to compile

@333fred 333fred Jun 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are good to include, but neither would have worked before the original lambda changes. The breaks I'm referring to are when an inner scope method suddenly becomes applicable where it wasn't before, such as an instance generic now working instead of going to an extension method, or a derived overload now working where previously it went to a base type.


An *explicit parameter type inference* is made *from* an expression `E` *to* a type `T` in the following way:

- If `E` is an explicitly typed anonymous function \***or method group with a unique signature** with parameter types `U₁...Uᵥ` and `T` is a delegate type or expression tree type with parameter types `V₁...Vᵥ` then for each `Uᵢ` an *exact inference* is made *from* `Uᵢ` *to* the corresponding `Vᵢ`.