RefactorMCP catalog

Convert Lambda to Method Group

Replaces a lambda that only passes its parameters on to a method, such as n => Format(n), with the method group Format. The reverse of Convert Method Group to Lambda.

Target

The lambda, by a caret anywhere inside it.

Precondition

  • The lambda, or anonymous method, is not async, and its body is a single call: an expression body, or a block holding only that call as a statement or a return.
  • The call passes exactly the lambda's parameters, in order, each by its simple name, without names and with the ref kind each was declared with.
  • The call is of a method, not of a delegate, and the expression naming the method does not use the lambda's parameters, so name => name.Trim() is not a candidate.
  • The receiver, if any, has the same value whenever the lambda 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 lambda evaluates its receiver on every call; a method group evaluates it once.
  • As a method group, the expression binds to 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 lambda can convert its arguments, as from int to long; a method group cannot.

Transformation

  • The lambda is replaced by the expression naming the method, with its receiver and any type arguments as written in the call: (a, b) => Add(a, b) becomes Add, x => _formatter.Format(x) becomes _formatter.Format.

Preserved

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

Limitations

  • A receiver that is null throws when the method group is created, where the lambda would have thrown only when called.
  • A readonly field is treated as stable even while a constructor is still assigning it.

Error codes

CodeMeaning
not-a-lambdathe caret is not inside a lambda
not-a-forwarding-callthe body is not a single call passing the parameters in order
unstable-receiverthe receiver could change between creating the delegate and calling it
resolution-changesthe method group 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.

#argument-conversion-rejected

Refuses a lambda whose argument is converted to the method's parameter type, which a method group cannot do

refusal
TargetSample.cs, the /*^*/ caret
Refusesresolution-changes; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public string Run()
6 {
7 Func<int, string> describe = /*^*/n => Describe(n);
8 return describe(3);
9 }
10
11 private static string Describe(long value) => value.ToString();
12 }

#block-body

A lambda whose block holds the single call becomes the method group, choosing the same overload

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(/*^*/name => { Console.WriteLine(name); });
8+ names.ForEach(Console.WriteLine);
99 }
1010 }

#changed-arguments-rejected

Refuses a lambda that passes something other than its parameters

refusal
TargetSample.cs, the /*^*/ caret
Refusesnot-a-forwarding-call; every file is left unchanged
Sample.csinput
1 using System.Collections.Generic;
2 using System.Linq;
3
4 public class Sample
5 {
6 public List<string> Describe(int[] numbers)
7 {
8 return numbers.Select(/*^*/n => Format(n + 1)).ToList();
9 }
10
11 private static string Format(int number) => "#" + number;
12 }

#generic-method

A lambda calling a generic method becomes the method group, whose type argument is inferred the same way

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

#instance-method

A lambda calling an instance method of this becomes the method group

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System.Collections.Generic;
22 using System.Linq;
33
44 public class Sample
55 {
66 private int _minimum = 3;
77
88 public int CountValid(IEnumerable<int> values)
99 {
10− return values.Count(/*^*/value => this.IsValid(value));
10+ return values.Count(this.IsValid);
1111 }
1212
1313 private bool IsValid(int value) => value >= _minimum;
1414 }

#keeps-comments

Comments around the lambda stay around the method group

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− /*^*/n => Format(n)) // formatted
11+ Format) // formatted
1212 .ToList();
1313 }
1414
1515 private static string Format(int number) => "#" + number;
1616 }

#not-a-lambda-rejected

Refuses a caret that is not inside a lambda

refusal
TargetSample.cs, the /*^*/ caret
Refusesnot-a-lambda; 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 lambda over nullable references becomes the method group 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(/*^*/name => Trim(name)).ToList();
8+ return names.Select(Trim).ToList();
99 }
1010
1111 private static string? Trim(string? name) => name?.Trim();
1212 }

#parameter-as-receiver-rejected

Refuses a lambda that calls a method on its parameter

refusal
TargetSample.cs, the /*^*/ caret
Refusesnot-a-forwarding-call; every file is left unchanged
Sample.csinput
1 using System.Collections.Generic;
2 using System.Linq;
3
4 public class Sample
5 {
6 public List<string> Clean(List<string> names)
7 {
8 return names.Select(/*^*/name => name.Trim()).ToList();
9 }
10 }

#readonly-field-receiver

A lambda calling a method on a readonly field becomes that field's method group

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(/*^*/name => _formatter.Format(name)).ToList();
15+ return names.Select(_formatter.Format).ToList();
1616 }
1717 }

#reassigned-receiver-rejected

Refuses a local receiver that is assigned again, which the lambda reads 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 = /*^*/text => formatter.Format(text);
14 formatter = second;
15 return format("a");
16 }
17 }

#several-parameters

A lambda passing several parameters in order becomes the method group

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 = (a, b) /*^*/=> Add(a, b);
7+ Func<int, int, int> combine = Add;
88 return combine(2, 3);
99 }
1010
1111 private static int Add(int left, int right) => left + right;
1212 }

#static-method

A lambda passing its parameter to a static method becomes the method group

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(/*^*/n => Format(n)).ToList();
8+ return numbers.Select(Format).ToList();
99 }
1010
1111 private static string Format(int number) => "#" + number;
1212 }

#unstable-receiver-rejected

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

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(/*^*/name => CreateFormatter().Format(name)).ToList();
14 }
15
16 private static Formatter CreateFormatter() => new Formatter();
17 }