RefactorMCP catalog

Inline Method

Replaces every call of a method with the method's body, across the solution, and deletes the method. The reverse of Extract Method. A read-only property whose getter computes its value is inlined the same way at every read.

Target

The method, by symbol, such as M:Shop.Order.Total(System.Int32); overloads are told apart by their signature. Or the property, such as P:Shop.Order.Total.

Precondition

  • The method has a body, is not virtual, abstract, an override or an interface implementation, is not async or an iterator, and does not call itself.
  • Every reference to the method is a call. A method group, such as one converted to a delegate, has no call to inline.
  • A method returning a value has a single expression: an expression body, or a block holding only return expression;. Its calls are expressions whose value is used.
  • A method returning a value from several statements returns only at its end, and each call starts its statement: it is the whole statement, the initializer of a local declaring nothing else, the value assigned to a simple target, or the value returned.
  • A void method returns, if at all, only at its end. Each call is a statement of its own.
  • Every member and type the method names is accessible at each call.
  • Locals the inlined code declares, including locals for arguments, do not clash with names at the call site.
  • A call passes no ref, out or in argument and no params array, and a call on an object other than this names it with a simple expression when the method reaches this more than once.

For a property:

  • It is not an auto-property, which stores its value, and has no setter or init accessor, whose writes could not be inlined. Its getter, or its expression body, is a single expression: => expression, or get { return expression; }.
  • It is not virtual, abstract, an override or an interface implementation, and its getter does not read the property itself.
  • Every member and type the getter names is accessible at each read, and a read on an object other than this names it with a simple expression when the getter reaches this more than once.
  • A read through ?. is of a getter that starts from a member of the object, such as Department.Manager, and reaches this nowhere else, so that the rest of the getter can follow the ?..

Transformation

  • A value-returning method's expression replaces the call, parenthesised only where precedence requires. A void method's statements replace the call statement, in a new block when the call was the body of an if or loop without braces.
  • For a method returning a value from several statements, the statements before its return go before the call's statement and the returned expression replaces the call. A call whose value is discarded keeps the expression only when evaluating it has side effects, as a discard.
  • Each parameter is replaced by its argument, or by the parameter's default when the call leaves it out. An argument with side effects that the method reads more than once or not at all, or that the method assigns, is first evaluated into a local named after the parameter and declared with its type. When several arguments have side effects, each gets a local so they run in the order written.
  • Type parameters are replaced by the call's type arguments, inferred or written.
  • Members the method reaches through this are reached through the call's receiver. Types and static members are qualified as much as the call site needs, so a call in a file without the method's using directives still compiles.
  • Calls nested in another call's arguments are inlined too.
  • The method is deleted, with its comments.
  • A property's getter expression replaces each read, parenthesised only where precedence requires, with members it reaches through this reached through the read's receiver: order.Total becomes order.Subtotal + order.Tax in parentheses where needed, and person?.Manager becomes person?.Department.Manager. The property is deleted, with its comments.

Preserved

  • Behaviour at every call: arguments are evaluated once, in order, before the method's code that depends on them.
  • Comments inside the method travel with its statements, below any comment above the call.
  • Other overloads of the method.

Limitations

  • A value-returning method with more than one statement is refused where its call sits inside a larger expression, rather than introducing a local for the result.
  • Extension methods the method calls need their namespace imported at the call site; no using directive is added.
  • An argument without side effects is substituted even when it reads a field the method changes before reading the parameter.
  • Only whole-method inlining is covered; inlining a single call site and keeping the method is not.
  • A property is only inlined when it cannot be written; a property with a setter is refused rather than turned into a field.

Error codes

CodeMeaning
recursive-methodthe method calls itself
polymorphic-methodthe method is virtual, an override or an interface implementation
unsupported-methodthe method is async or an iterator
method-group-referencethe method is used without being called
inaccessible-memberthe method uses a member or type a call site cannot access
multiple-statementsa value-returning method's body is more than a single return, and a call does not start its statement
early-returna void method returns before its last statement
name-conflicta local the inlined code declares clashes with a name at the call site
unsupported-calla void method's call is not a statement of its own
writable-propertythe property has a setter or init accessor
auto-propertythe property is an auto-property, with no getter body to inline
conditional-accessa read through ?. is of a getter that does not start from one member of the object

Cases

·

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

#argument-with-side-effects

An argument with side effects that the method reads twice is evaluated once, into a local named after the parameter

success
Targetsymbol M:Sample.Show(System.Int32)
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 private int _counter;
66
77 public void Run()
88 {
9− Show(Next());
10− }
11−
12− private void Show(int value)
13− {
9+ int value = Next();
1410 Console.WriteLine(value);
1511 Console.WriteLine(value * 2);
1612 }
1713
1814 private int Next() => ++_counter;
1915 }

#async-rejected

Refuses an async method, whose awaits belong to it

refusal
Targetsymbol M:Sample.Wait
Refusesunsupported-method; every file is left unchanged
Sample.csinput
1 using System.Threading.Tasks;
2
3 public class Sample
4 {
5 public async Task Run()
6 {
7 await Wait();
8 }
9
10 private async Task Wait()
11 {
12 await Task.Delay(1);
13 }
14 }

#call-in-another-file

A call on another object in another file has the members the method used qualified with that object

success
Targetsymbol M:Shop.Order.Describe
Checkout.csmodified
11 namespace Shop
22 {
33 public class Checkout
44 {
5− public string Label(Order order) => "Buying " + order.Describe();
5+ public string Label(Order order) => "Buying " + $"{order.Name} x {order.Quantity}";
66 }
77 }
Order.csmodified
11 namespace Shop
22 {
33 public class Order
44 {
55 public string Name { get; set; } = "";
66
77 public int Quantity { get; set; }
8−
9− public string Describe() => $"{Name} x {Quantity}";
108 }
119 }

#call-not-statement-rejected

Refuses a call to a void method that is a lambda's body rather than a statement

refusal
Targetsymbol M:Sample.Greet
Refusesunsupported-call; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public Action Later() => () => Greet();
6
7 private void Greet()
8 {
9 Console.WriteLine("Hello");
10 Console.WriteLine("World");
11 }
12 }

#early-return-rejected

Refuses a method that returns before its last statement, which would return from the caller instead

refusal
Targetsymbol M:Sample.Log(System.String)
Refusesearly-return; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public void Run(string text)
6 {
7 Log(text);
8 Console.WriteLine("Done");
9 }
10
11 private void Log(string text)
12 {
13 if (text.Length == 0)
14 return;
15 Console.WriteLine(text);
16 }
17 }

#expression-in-expressions

A method returning an expression is inlined into the expressions that call it, parenthesised only where needed

success
Targetsymbol M:Sample.Doubled(System.Int32)
Sample.csmodified
11 public class Sample
22 {
33 public int Calc(int a, int b)
44 {
5− return Doubled(a) + Doubled(b + 1);
6− }
7−
8− private int Doubled(int x)
9− {
10− return x * 2;
5+ return a * 2 + (b + 1) * 2;
116 }
127 }

#generic-method

The method's type parameters are replaced by the type arguments the call inferred

success
Targetsymbol M:Sample.Pair``1(``0,``0)
Sample.csmodified
11 using System.Collections.Generic;
22
33 public class Sample
44 {
5− public List<string> Names() => Pair("a", "b");
6−
7− private static List<T> Pair<T>(T first, T second) => new List<T> { first, second };
5+ public List<string> Names() => new List<string> { "a", "b" };
86 }

#inaccessible-member-rejected

Refuses when a call site cannot see a private member the method uses

refusal
Targetsymbol M:Shop.Order.Total
Refusesinaccessible-member; every file is left unchanged
Checkout.csinput
1 namespace Shop
2 {
3 public class Checkout
4 {
5 public int Pay(Order order) => order.Total();
6 }
7 }
Order.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 private int _price = 5;
6
7 public int Total() => _price * 2;
8 }
9 }

#inherited-nested-calls

Calls from a derived class, one nested in the other's argument, are both inlined

success
Targetsymbol M:Shop.Pricing.Twice(System.Int32)
Bulk.csmodified
11 namespace Shop
22 {
33 public class Bulk : Pricing
44 {
5− public int Four(int amount) => Twice(Twice(amount));
5+ public int Four(int amount) => amount * 2 * 2;
66 }
77 }
Pricing.csmodified
11 namespace Shop
22 {
33 public class Pricing
44 {
5− protected int Twice(int amount) => amount * 2;
65 }
76 }

#keeps-comments

Comments in the method travel with its statements, below the comment above the call

success
Targetsymbol M:Sample.Greet
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public void Run()
66 {
77 // Say hello first.
8− Greet();
8+ // The greeting.
9+ Console.WriteLine("Hello"); // always
910 Console.WriteLine("Done");
1011 }
11−
12− private void Greet()
13− {
14− // The greeting.
15− Console.WriteLine("Hello"); // always
16− }
1712 }

#method-group-rejected

Refuses a method that is also used as a delegate, which has no call to inline

refusal
Targetsymbol M:Sample.Doubled(System.Int32)
Refusesmethod-group-reference; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public int Calc(int a)
6 {
7 Func<int, int> twice = Doubled;
8 return twice(a) + Doubled(a);
9 }
10
11 private int Doubled(int x) => x * 2;
12 }

#multiple-statements-rejected

Refuses to inline a method that computes its result in several statements into an expression

refusal
Targetsymbol M:Sample.Area(System.Int32,System.Int32)
Refusesmultiple-statements; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Total(int w, int h) => Area(w, h) + 1;
4
5 private int Area(int width, int height)
6 {
7 var area = width * height;
8 return area;
9 }
10 }

#name-conflict-rejected

Refuses when a local the method declares would clash with one at the call site

refusal
Targetsymbol M:Sample.Print(System.Int32)
Refusesname-conflict; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public void Run(int a)
6 {
7 var total = a + 1;
8 Print(total);
9 }
10
11 private void Print(int value)
12 {
13 var total = value * 2;
14 Console.WriteLine(total);
15 }
16 }

#nullable-context

Inlines a method over nullable values without new warnings

success
Targetsymbol M:Sample.OrDefault(System.String)
Projectnullable enable
Sample.csmodified
11 public class Sample
22 {
3− public string Describe(string? name) => OrDefault(name);
4−
5− private static string OrDefault(string? value) => value ?? "anonymous";
3+ public string Describe(string? name) => name ?? "anonymous";
64 }

#overload

Inlines one overload, chosen by its signature, and leaves the other

success
Targetsymbol M:Sample.Format(System.Int32)
Sample.csmodified
11 public class Sample
22 {
3− public string Show(int count, string name) => Format(count) + Format(name);
4−
5− private string Format(int value) => "#" + value;
3+ public string Show(int count, string name) => "#" + count + Format(name);
64
75 private string Format(string value) => "'" + value + "'";
86 }

#property-auto-rejected

Refuses an auto-property, whose value is stored rather than computed

refusal
Targetsymbol P:Shop.Order.Id
Refusesauto-property; every file is left unchanged
Order.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 public int Id { get; }
6
7 public string Label() => "#" + Id;
8 }
9 }

#property-conditional-access

A read through ?. of a property whose getter starts from a member of the object keeps the ?. before that member

success
Targetsymbol P:Company.Person.Manager
Person.csmodified
11 namespace Company
22 {
33 public class Department
44 {
55 public Employee Manager { get; set; }
66 }
77
88 public class Employee
99 {
1010 public string Name { get; set; }
1111 }
1212
1313 public class Person
1414 {
1515 public Department Department { get; set; }
16−
17− public Employee Manager => Department.Manager;
1816 }
1917 }
Report.csmodified
11 namespace Company
22 {
33 public class Report
44 {
55 public string Describe(Person person)
66 {
7− return person?.Manager.Name;
7+ return person?.Department.Manager.Name;
88 }
99 }
1010 }

#property-conditional-access-rejected

Refuses a read through ?. of a property whose getter does not start from one member of the object

refusal
Targetsymbol P:Company.Person.Initials
Refusesconditional-access; every file is left unchanged
Person.csinput
1 namespace Company
2 {
3 public class Person
4 {
5 public string First { get; set; }
6
7 public string Last { get; set; }
8
9 public string Initials => First.Substring(0, 1) + Last.Substring(0, 1);
10 }
11
12 public class Report
13 {
14 public string Describe(Person person) => person?.Initials;
15 }
16 }

#property-expression-bodied

A read-only property is inlined at every read, in its class and through a receiver in another file, parenthesised where precedence needs it, and deleted

success
Targetsymbol P:Shop.Order.Total
Invoice.csmodified
11 namespace Shop
22 {
33 public class Invoice
44 {
5− public decimal Due(Order order) => order.Total * 1.1m;
5+ public decimal Due(Order order) => (order.Subtotal + order.Tax) * 1.1m;
66 }
77 }
Order.csmodified
11 namespace Shop
22 {
33 public class Order
44 {
55 public decimal Subtotal { get; set; }
66
77 public decimal Tax { get; set; }
88
9− public decimal Total => Subtotal + Tax;
10−
11− public string Describe() => "Total: " + Total;
9+ public string Describe() => "Total: " + (Subtotal + Tax);
1210 }
1311 }

#property-getter-block

A getter with a block body returning one expression is inlined, and its comments above the property go with it

success
Targetsymbol P:Shop.Cart.IsEmpty
Cart.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
55 public class Cart
66 {
77 private readonly List<string> _items = new List<string>();
88
9− // Whether anything has been added.
10− public bool IsEmpty
11− {
12− get { return _items.Count == 0; }
13− }
14−
159 public string Summary()
1610 {
17− if (!IsEmpty)
11+ if (!(_items.Count == 0))
1812 {
1913 return _items.Count + " items";
2014 }
2115
2216 return "empty";
2317 }
2418 }
2519 }

#property-inaccessible-member-rejected

Refuses a property whose getter reads a private field that a read in another class cannot access

refusal
Targetsymbol P:Shop.Cart.Count
Refusesinaccessible-member mentioning “_items”; every file is left unchanged
Cart.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Cart
6 {
7 private readonly List<string> _items = new List<string>();
8
9 public int Count => _items.Count;
10 }
11
12 public class Checkout
13 {
14 public bool CanPay(Cart cart) => cart.Count > 0;
15 }
16 }

#property-nullable

With nullable annotations, the inlined getter keeps its null handling at the read

success
Targetsymbol P:People.Person.Display
Projectnullable enable
Person.csmodified
11 namespace People
22 {
33 public class Person
44 {
55 public string Name { get; set; } = "";
66
77 public string? Nickname { get; set; }
8−
9− public string Display => Nickname ?? Name;
108 }
119
1210 public class Badge
1311 {
14− public int Width(Person person) => person.Display.Length * 8;
12+ public int Width(Person person) => (person.Nickname ?? person.Name).Length * 8;
1513 }
1614 }

#property-virtual-rejected

Refuses a virtual property, whose reads may run an override

refusal
Targetsymbol P:Shop.Order.Total
Refusespolymorphic-method; every file is left unchanged
Order.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 public decimal Subtotal { get; set; }
6
7 public virtual decimal Total => Subtotal;
8
9 public string Describe() => "Total: " + Total;
10 }
11 }

#property-writable-rejected

Refuses a property with a setter, whose writes could not be inlined

refusal
Targetsymbol P:Shop.Order.Quantity
Refuseswritable-property; every file is left unchanged
Order.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 private int _quantity;
6
7 public int Quantity
8 {
9 get => _quantity;
10 set => _quantity = value;
11 }
12
13 public int Twice() => Quantity * 2;
14 }
15 }

#recursive-rejected

Refuses a method that calls itself

refusal
Targetsymbol M:Sample.Factorial(System.Int32)
Refusesrecursive-method; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Six() => Factorial(3);
4
5 private int Factorial(int n) => n <= 1 ? 1 : n * Factorial(n - 1);
6 }

#statements-before-result

A method that computes its result in several statements is inlined where each call starts its statement: the statements go first and the result takes the call's place, or is dropped when discarded

success
Targetsymbol M:Sample.Counter.Next
Counter.csmodified
11 namespace Sample
22 {
33 public class Counter
44 {
55 private int _count;
66
77 public int Skip()
88 {
9− var first = Next();
10− Next();
11− var third = Next();
9+ _count++;
10+ var first = _count;
11+ _count++;
12+ _count++;
13+ var third = _count;
1214 return first + third;
13− }
14−
15− private int Next()
16− {
17− _count++;
18− return _count;
1915 }
2016 }
2117 }

#types-need-qualifying

A type the method names through a using the calling file lacks is qualified at the call site

success
Targetsymbol M:Shop.Report.Line(System.String)
Printer.csmodified
11 namespace Shop
22 {
33 public class Printer
44 {
5− public string Print(string text) => Report.Line(text);
5+ public string Print(string text) => new System.Text.StringBuilder(text).Append('.').ToString();
66 }
77 }
Report.csmodified
11 using System.Text;
22
33 namespace Shop
44 {
55 public static class Report
66 {
7− public static string Line(string text) => new StringBuilder(text).Append('.').ToString();
87 }
98 }

#virtual-rejected

Refuses a virtual method, whose calls may reach an override

refusal
Targetsymbol M:Animal.Sound
Refusespolymorphic-method; every file is left unchanged
Sample.csinput
1 public class Animal
2 {
3 public virtual string Sound() => "...";
4
5 public string Speak() => Sound() + "!";
6 }
7
8 public class Dog : Animal
9 {
10 public override string Sound() => "Woof";
11 }

#void-method

Replaces a call statement with the method's statements, its parameters replaced by the arguments, and deletes the method

success
Targetsymbol M:Sample.Greet(System.String)
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public void Run(string user)
66 {
7− Greet(user);
7+ Console.WriteLine("Hello");
8+ Console.WriteLine(user);
89 Console.WriteLine("Done");
910 }
10−
11− private void Greet(string name)
12− {
13− Console.WriteLine("Hello");
14− Console.WriteLine(name);
15− }
1611 }