RefactorMCP catalog

Convert to Expression Body

Replaces a block body holding a single statement with an expression body. The reverse of Convert to Block Body.

Target

The declaration, by symbol: a method, constructor, operator, property, indexer or accessor (M:Sample.set_Name(System.String) for a setter). A local function, which has no symbol id, by a caret on its name.

Precondition

  • The declaration has a block body, not an expression body and not none, as an abstract member or an auto-property has.
  • The block holds exactly one statement: return expression;, an expression statement, or throw expression;.
  • The block contains no preprocessor directive.
  • A property or indexer has a get accessor and nothing else, with no modifiers or attributes on it. A property with a setter as well is converted one accessor at a time.

Transformation

  • { return expression; } and { expression; } become => expression;.
  • { throw expression; } becomes => throw expression;.
  • A property or indexer whose only accessor is a get accessor becomes an expression-bodied property or indexer: public string Name => _name;.
  • The expression follows the header on the same line. An expression spanning several lines keeps its line breaks, shifted to follow the header.
  • A constructor initializer and a generic constraint clause stay before the arrow.

Preserved

  • Behaviour: an expression body means exactly what the single statement did.
  • Comments inside the block move to lines of their own above the declaration, below any documentation comment; a comment after the statement stays at the end of the line.

Limitations

  • A comment between return and its expression, or after the header before the block, is not kept.
  • Accessors with attributes or modifiers of their own keep their property's accessor list; only the accessor itself can be converted.

Error codes

CodeMeaning
not-single-statementthe body is not a single return, expression or throw statement
several-accessorsthe property has more than a plain get accessor
already-expression-bodiedthe declaration already has an expression body
directive-in-bodythe body contains a preprocessor directive
no-bodythe declaration has no body, such as an abstract method
not-a-member-with-bodythe target is not a method, property, accessor, constructor, operator or local function

Cases

·

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

#accessor

One accessor of a property with a getter and a setter becomes expression-bodied

success
Targetsymbol M:Sample.set_Name(System.String)
Sample.csmodified
11 public class Sample
22 {
33 private string _name = "";
44
55 public string Name
66 {
77 get { return _name; }
8− set
9− {
10− _name = value.Trim();
11− }
8+ set => _name = value.Trim();
129 }
1310 }

#already-expression-bodied-rejected

Refuses a member that is already expression-bodied

refusal
Targetsymbol M:Sample.Total
Refusesalready-expression-bodied; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 private int _a;
4
5 public int Total() => _a;
6 }

#constructor

A constructor with a single assignment and an initializer becomes expression-bodied

success
Targetsymbol M:Shop.Order.#ctor(System.Int32)
Order.csmodified
11 namespace Shop;
22
33 public class Entity
44 {
55 }
66
77 public class Order : Entity
88 {
99 private readonly int _quantity;
1010
11− public Order(int quantity) : base()
12− {
13− _quantity = quantity;
14− }
11+ public Order(int quantity) : base() => _quantity = quantity;
1512 }

#directive-in-body-rejected

Refuses a body containing a preprocessor directive, which would no longer surround the same code

refusal
Targetsymbol M:Sample.Log(System.String)
Refusesdirective-in-body; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public void Log(string message)
6 {
7 #if DEBUG
8 Console.WriteLine("debug: " + message);
9 #else
10 Console.WriteLine(message);
11 #endif
12 }
13 }

#generic-method

A generic method keeps its constraint clause before the expression body

success
Targetsymbol M:Sample.Larger``1(``0,``0)
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
5− public T Larger<T>(T a, T b) where T : IComparable<T>
6− {
7− return a.CompareTo(b) >= 0 ? a : b;
8− }
5+ public T Larger<T>(T a, T b) where T : IComparable<T> => a.CompareTo(b) >= 0 ? a : b;
96 }

#keeps-comments

Comments inside the block move above the member, and a comment after the statement stays at the end of the line

success
Targetsymbol M:Sample.Total
Sample.csmodified
11 public class Sample
22 {
33 private int _a;
44 private int _b;
55
66 /// <summary>The sum of both parts.</summary>
7− public int Total()
8− {
9− // Overflow is not expected here.
10− return _a + _b; // unchecked
11− }
7+ // Overflow is not expected here.
8+ public int Total() => _a + _b; // unchecked
129
1310 public int Difference() => _a - _b;
1411 }

#local-function

A local function, targeted by a caret on its name, becomes expression-bodied

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 public int Total(int[] prices)
44 {
55 var sum = 0;
66 foreach (var price in prices)
77 sum += WithTax(price);
88 return sum;
99
10− static int /*^*/WithTax(int price)
11− {
12− return price * 120 / 100;
13− }
10+ static int WithTax(int price) => price * 120 / 100;
1411 }
1512 }

#multi-line-expression

An expression spanning several lines keeps its line breaks, indented under the member

success
Targetsymbol M:Sample.Total(System.Collections.Generic.List{System.Int32})
Sample.csmodified
11 using System.Collections.Generic;
22 using System.Linq;
33
44 public class Sample
55 {
6− public int Total(List<int> items)
7− {
8− return items
9− .Where(i => i > 0)
10− .Sum();
11− }
6+ public int Total(List<int> items) => items
7+ .Where(i => i > 0)
8+ .Sum();
129 }

#no-body-rejected

Refuses an abstract method, which has no body

refusal
Targetsymbol M:Shape.Area
Refusesno-body; every file is left unchanged
Shape.csinput
1 public abstract class Shape
2 {
3 public abstract double Area();
4 }

#not-a-member-rejected

Refuses a field, which has no body

refusal
Targetsymbol F:Sample._total
Refusesnot-a-member-with-body; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 private int _total;
4
5 public int Total => _total;
6 }

#nullable-context

A method returning a nullable reference converts without new warnings

success
Targetsymbol M:Sample.Find(System.String)
Projectnullable enable
Sample.csmodified
11 using System.Collections.Generic;
22
33 public class Sample
44 {
55 private readonly Dictionary<string, string> _names = new();
66
7− public string? Find(string key)
8− {
9− return _names.TryGetValue(key, out var name) ? name : null;
10− }
7+ public string? Find(string key) => _names.TryGetValue(key, out var name) ? name : null;
118 }

#operator

An operator returning a single expression becomes expression-bodied

success
Targetsymbol M:Shop.Money.op_Addition(Shop.Money,Shop.Money)
Money.csmodified
11 namespace Shop;
22
33 public readonly struct Money
44 {
55 public Money(decimal amount)
66 {
77 Amount = amount;
88 }
99
1010 public decimal Amount { get; }
1111
12− public static Money operator +(Money left, Money right)
13− {
14− return new Money(left.Amount + right.Amount);
15− }
12+ public static Money operator +(Money left, Money right) => new Money(left.Amount + right.Amount);
1613 }

#overload

Only the targeted overload is converted

success
Targetsymbol M:Sample.Format(System.Int32)
Sample.csmodified
11 public class Sample
22 {
33 public string Format(string text)
44 {
55 return text.Trim();
66 }
77
8− public string Format(int number)
9− {
10− return number.ToString();
11− }
8+ public string Format(int number) => number.ToString();
129 }

#read-only-property

A property with only a get accessor becomes an expression-bodied property

success
Targetsymbol P:Sample.Name
Sample.csmodified
11 public class Sample
22 {
33 private string _first = "";
44 private string _last = "";
55
6− public string Name
7− {
8− get
9− {
10− return _first + " " + _last;
11− }
12− }
6+ public string Name => _first + " " + _last;
137 }

#return-statement

A method whose block holds a single return becomes expression-bodied

success
Targetsymbol M:Sample.Total
Sample.csmodified
11 public class Sample
22 {
33 private int _a;
44 private int _b;
55
6− public int Total()
7− {
8− return _a + _b;
9− }
6+ public int Total() => _a + _b;
107 }

#several-accessors-rejected

Refuses a property with a setter as well as a getter; its accessors can be converted one at a time

refusal
Targetsymbol P:Sample.Name
Refusesseveral-accessors; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 private string _name = "";
4
5 public string Name
6 {
7 get { return _name; }
8 set { _name = value; }
9 }
10 }

#several-statements-rejected

Refuses a body with more than one statement

refusal
Targetsymbol M:Sample.Total
Refusesnot-single-statement; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 private int _a;
4 private int _b;
5
6 public int Total()
7 {
8 var sum = _a + _b;
9 return sum;
10 }
11 }

#throw-statement

A body that only throws becomes a throw expression

success
Targetsymbol M:Sample.Next
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
5− public int Next()
6− {
7− throw new NotSupportedException();
8− }
5+ public int Next() => throw new NotSupportedException();
96 }

#void-method

A void method whose block holds a single expression statement becomes expression-bodied

success
Targetsymbol M:Sample.Log(System.String)
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
5− public void Log(string message)
6− {
7− Console.WriteLine(message);
8− }
5+ public void Log(string message) => Console.WriteLine(message);
96 }