RefactorMCP catalog

Convert Method Group to Lambda

Replaces a method group used as a delegate, such as Format, with a lambda calling it, such as number => Format(number). The reverse of Convert Lambda to Method Group.

Target

The method group, by a caret on the method's name.

Precondition

  • The name is a method group converted to a delegate type, not a call.
  • The receiver, if any, has the same value whenever the delegate could run: no receiver, this, base, a type, a readonly field of such a receiver, or a local or value parameter never assigned after its declaration. A method group evaluates its receiver once; a lambda evaluates it on every call.
  • As a lambda, the expression calls the same method, converts to the same delegate type, and leaves the overload of any call it is passed to unchanged, with no new compile errors. A method group with a natural type, as in var format = Format;, has no lambda without parameter types to replace it.

Transformation

  • The lambda has one parameter per parameter of the delegate, named after the method's parameters, and calls the method with them in order, keeping the receiver and any type arguments written on the group.
  • A name already used by a local, a parameter or the receiver where the lambda goes gets a number: number becomes number1.
  • A single parameter is written without parentheses and a delegate without parameters gets ().
  • When a parameter is ref, out or in, every parameter is written with its type and the call passes each with its ref kind.

Preserved

  • Behaviour: the delegate calls the same method with the same arguments.
  • Comments before and after the method group.

Limitations

  • A readonly field is treated as stable even while a constructor is still assigning it.

Error codes

CodeMeaning
not-a-method-groupthe caret is not on a method group converted to a delegate
unstable-receiverthe receiver could change between creating the delegate and calling it
resolution-changesthe lambda would bind to a different method, delegate type or overload

Cases

·

Diffs show each file from before to after. /*[*/ … /*]*/ marks a selection and /*^*/ a caret; the runner removes them before the refactoring runs.

#field-receiver

A method group on a readonly field becomes a lambda calling the method on the field

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System.Collections.Generic;
22 using System.Linq;
33
44 public class Formatter
55 {
66 public string Format(string text) => text.ToUpperInvariant();
77 }
88
99 public class Sample
1010 {
1111 private readonly Formatter _formatter = new Formatter();
1212
1313 public List<string> FormatAll(List<string> names)
1414 {
15− return names.Select(_formatter./*^*/Format).ToList();
15+ return names.Select(text => _formatter.Format(text)).ToList();
1616 }
1717 }

#generic-method

A group of a generic method keeps its type arguments in the call

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System;
22 using System.Collections.Generic;
33
44 public class Sample
55 {
66 public List<object> Run()
77 {
8− Func<string, List<object>> wrap = /*^*/Wrap<object>;
8+ Func<string, List<object>> wrap = item => Wrap<object>(item);
99 return wrap("a");
1010 }
1111
1212 private static List<T> Wrap<T>(T item) => new List<T> { item };
1313 }

#keeps-comments

Comments around the method group stay around the lambda

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System.Collections.Generic;
22 using System.Linq;
33
44 public class Sample
55 {
66 public List<string> Describe(int[] numbers)
77 {
88 return numbers
99 .Select(
1010 // each as text
11− /*^*/Format) // formatted
11+ number => Format(number)) // formatted
1212 .ToList();
1313 }
1414
1515 private static string Format(int number) => "#" + number;
1616 }

#natural-type-rejected

Refuses a method group whose delegate type comes from the group itself, which a lambda without parameter types cannot provide

refusal
TargetSample.cs, the /*^*/ caret
Refusesresolution-changes; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public string Run()
4 {
5 var format = /*^*/Format;
6 return format(3);
7 }
8
9 private static string Format(int number) => "#" + number;
10 }

#no-parameters

A method group for a delegate without parameters becomes a lambda with an empty parameter list

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 private int _count;
66
77 public int Run()
88 {
9− Func<int> next = /*^*/Next;
9+ Func<int> next = () => Next();
1010 return next();
1111 }
1212
1313 private int Next() => ++_count;
1414 }

#not-a-method-group-rejected

Refuses a method that is called rather than used as a delegate

refusal
TargetSample.cs, the /*^*/ caret
Refusesnot-a-method-group; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public string Describe(int number)
4 {
5 return /*^*/Format(number);
6 }
7
8 private static string Format(int number) => "#" + number;
9 }

#nullable-context

A method group over nullable references becomes a lambda without new warnings

success
TargetSample.cs, the /*^*/ caret
Projectnullable enable
Sample.csmodified
11 using System.Collections.Generic;
22 using System.Linq;
33
44 public class Sample
55 {
66 public List<string?> Normalise(List<string?> names)
77 {
8− return names.Select(/*^*/Trim).ToList();
8+ return names.Select(name => Trim(name)).ToList();
99 }
1010
1111 private static string? Trim(string? name) => name?.Trim();
1212 }

#out-parameter

A delegate with an out parameter gets an explicitly typed lambda passing it by out

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public delegate bool Parser(string text, out int value);
22
33 public class Sample
44 {
55 public int Run()
66 {
7− Parser parse = /*^*/TryParse;
7+ Parser parse = (string text, out int value) => TryParse(text, out value);
88 return parse("7", out var parsed) ? parsed : 0;
99 }
1010
1111 private static bool TryParse(string text, out int value) => int.TryParse(text, out value);
1212 }

#overloaded-method

A group of an overloaded method becomes a lambda calling the overload the delegate chose

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System;
22 using System.Collections.Generic;
33
44 public class Sample
55 {
66 public void Print(List<string> names)
77 {
8− names.ForEach(Console./*^*/WriteLine);
8+ names.ForEach(value => Console.WriteLine(value));
99 }
1010 }

#reassigned-receiver-rejected

Refuses a local receiver that is assigned again, which a lambda would read when it runs

refusal
TargetSample.cs, the /*^*/ caret
Refusesunstable-receiver; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Formatter
4 {
5 public string Format(string text) => text.ToUpperInvariant();
6 }
7
8 public class Sample
9 {
10 public string Run(Formatter first, Formatter second)
11 {
12 var formatter = first;
13 Func<string, string> format = formatter./*^*/Format;
14 formatter = second;
15 return format("a");
16 }
17 }

#several-parameters

A method group for a delegate with several parameters becomes a lambda with a parenthesised parameter list

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public int Run()
66 {
7− Func<int, int, int> combine = /*^*/Add;
7+ Func<int, int, int> combine = (left, right) => Add(left, right);
88 return combine(2, 3);
99 }
1010
1111 private static int Add(int left, int right) => left + right;
1212 }

#static-method

A method group becomes a lambda calling the method, with its parameter's name

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System.Collections.Generic;
22 using System.Linq;
33
44 public class Sample
55 {
66 public List<string> Describe(int[] numbers)
77 {
8− return numbers.Select(/*^*/Format).ToList();
8+ return numbers.Select(number => Format(number)).ToList();
99 }
1010
1111 private static string Format(int number) => "#" + number;
1212 }

#unique-parameter-name

A parameter name already used where the lambda goes gets a number

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System.Collections.Generic;
22 using System.Linq;
33
44 public class Sample
55 {
66 public List<string> Describe(int[] numbers, int number)
77 {
8− return numbers.Where(n => n != number).Select(/*^*/Format).ToList();
8+ return numbers.Where(n => n != number).Select(number1 => Format(number1)).ToList();
99 }
1010
1111 private static string Format(int number) => "#" + number;
1212 }

#unstable-receiver-rejected

Refuses a receiver with side effects, which the method group evaluates once and a lambda would evaluate on every call

refusal
TargetSample.cs, the /*^*/ caret
Refusesunstable-receiver; every file is left unchanged
Sample.csinput
1 using System.Collections.Generic;
2 using System.Linq;
3
4 public class Formatter
5 {
6 public string Format(string text) => text.ToUpperInvariant();
7 }
8
9 public class Sample
10 {
11 public List<string> FormatAll(List<string> names)
12 {
13 return names.Select(CreateFormatter()./*^*/Format).ToList();
14 }
15
16 private static Formatter CreateFormatter() => new Formatter();
17 }