RefactorMCP catalog

Move Multiple Methods

Moves several methods of a class to another type in one operation: each instance method through a field, property or parameter of the target type, each static method to the target type by name.

Recipe

For each method, callees before their callers:

  • move-instance-method for an instance method: "target": { "symbol": "M:Shop.Customer.Street" }, "arguments": { "via": "_address" };
  • move-static-method for a static method: "target": { "symbol": "M:Shop.Order.Round(System.Decimal)" }, "arguments": { "to": "Shop.Pricing" }.

Every step passes the same stub choice. Moving callees first matters without stubs: the callee's move rewrites the caller's call to go through the via (_address.Street()), and the caller's move then turns that into a call on this in the target. Methods that call each other in a cycle move in the order they were named.

Arguments

ArgumentRequiredMeaning
methodsyesThe methods to move, by name; a name moves every overload
viaone of via and toThe field or property to move instance methods through; static methods move to its type
toone of via and toThe target type: instance methods move through the one field, property or parameter of that type, static methods to it
stubnotrue (the default) leaves a delegating method for each moved method; false updates the callers

The target is the class declaring the methods, by symbol: "target": { "symbol": "T:Shop.Customer" }.

Precondition

  • Every named method exists in the class.
  • Each move meets the precondition of Move Instance Method or Move Static Method, checked against the code as the earlier moves left it.

Transformation

  • Each method moves as its primitive moves it: uses of the via become this, uses of what stays behind go through a parameter of the old class, private members become internal, and static members the method uses are qualified by their type.
  • With stubs, each old method delegates to the moved one. A moved method that calls another moved method reaches it through the other's stub (customer.Street()).
  • Without stubs, every call is rewritten to reach the moved method.

Preserved

  • The behaviour of every call.
  • Comments and documentation of the moved methods.

Limitations

  • With stubs, calls between moved methods still go through the old class's stubs rather than straight to the moved methods, as the recipe leaves them.
  • A move that refuses part way through is reported by its step number in the callee-first order, not the order the methods were named in.

Error codes

CodeMeaning
method-not-founda named method does not exist in the class
via-not-foundthe class has no field or property with the via's name
no-reference-to-targetno field, property or parameter has the to type
polymorphic-methoda method is virtual, abstract, an override or an interface implementation
uses-protected-membera method uses a protected member of the class
via-not-accessiblewithout stubs, a caller cannot reach the via
member-existsthe target already has a method with that name and parameters

A refusal in any step leaves every file as it was, and names the step that refused.

Cases

·

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

#callees-first

Named caller first, the methods still move callee first, with the same result as the recipe

success
Targetsymbol T:Shop.Customer
Arguments
methods["Label", "Street"]
via"_address"
Address.csmodified
11 namespace Shop
22 {
33 public class Address
44 {
55 public int Number { get; set; }
66
77 public string Road { get; set; }
88
99 public string Town { get; set; }
10+
11+ // The house number goes first.
12+ public string Street() => Number + " " + Road;
13+
14+ public string Label(Customer customer) => customer.Name + ", " + customer.Street() + ", " + Town;
1015 }
1116 }
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 private readonly Address _address = new Address();
66
77 public string Name { get; set; }
88
9− public string Label() => Name + ", " + Street() + ", " + _address.Town;
9+ public string Label() => _address.Label(this);
1010
11− // The house number goes first.
12− public string Street() => _address.Number + " " + _address.Road;
11+ public string Street() => _address.Street();
1312 }
1413 }
Envelope.csunchanged
11 namespace Shop
22 {
33 public class Envelope
44 {
55 public string Front(Customer customer) => customer.Label();
66 }
77 }

#method-not-found-rejected

A named method does not exist in the class

refusal
Targetsymbol T:Shop.Customer
Arguments
methods["Street", "Postcode"]
via"_address"
Refusesmethod-not-found mentioning “Postcode”; every file is left unchanged
Address.csinput
1 namespace Shop
2 {
3 public class Address
4 {
5 public int Number { get; set; }
6
7 public string Road { get; set; }
8
9 public string Town { get; set; }
10 }
11 }
Customer.csinput
1 namespace Shop
2 {
3 public class Customer
4 {
5 private readonly Address _address = new Address();
6
7 public string Name { get; set; }
8
9 public string Label() => Name + ", " + Street() + ", " + _address.Town;
10
11 // The house number goes first.
12 public string Street() => _address.Number + " " + _address.Road;
13 }
14 }
Envelope.csinput
1 namespace Shop
2 {
3 public class Envelope
4 {
5 public string Front(Customer customer) => customer.Label();
6 }
7 }

#polymorphic-method-rejected

Label is virtual and cannot move, so its move refuses after Street has moved, and every file is put back

refusal
Targetsymbol T:Shop.Customer
Arguments
methods["Label", "Street"]
via"_address"
Refusespolymorphic-method mentioning “step 2 (move-instance-method)”; every file is left unchanged
Address.csinput
1 namespace Shop
2 {
3 public class Address
4 {
5 public int Number { get; set; }
6
7 public string Road { get; set; }
8
9 public string Town { get; set; }
10 }
11 }
Customer.csinput
1 namespace Shop
2 {
3 public class Customer
4 {
5 private readonly Address _address = new Address();
6
7 public string Name { get; set; }
8
9 public virtual string Label() => Name + ", " + Street() + ", " + _address.Town;
10
11 // The house number goes first.
12 public string Street() => _address.Number + " " + _address.Road;
13 }
14 }
Envelope.csinput
1 namespace Shop
2 {
3 public class Envelope
4 {
5 public string Front(Customer customer) => customer.Label();
6 }
7 }

#recipe

Moves Street, then Label which calls it, onto Address through the address field, each leaving a delegating stub, as the recipe of primitive steps

successrecipe
Recipe
  1. move-instance-method on symbol M:Shop.Customer.Street
    via"_address"
  2. move-instance-method on symbol M:Shop.Customer.Label
    via"_address"
Address.csmodified
11 namespace Shop
22 {
33 public class Address
44 {
55 public int Number { get; set; }
66
77 public string Road { get; set; }
88
99 public string Town { get; set; }
10+
11+ // The house number goes first.
12+ public string Street() => Number + " " + Road;
13+
14+ public string Label(Customer customer) => customer.Name + ", " + customer.Street() + ", " + Town;
1015 }
1116 }
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 private readonly Address _address = new Address();
66
77 public string Name { get; set; }
88
9− public string Label() => Name + ", " + Street() + ", " + _address.Town;
9+ public string Label() => _address.Label(this);
1010
11− // The house number goes first.
12− public string Street() => _address.Number + " " + _address.Road;
11+ public string Street() => _address.Street();
1312 }
1413 }
Envelope.csunchanged
11 namespace Shop
22 {
33 public class Envelope
44 {
55 public string Front(Customer customer) => customer.Label();
66 }
77 }

#static-and-without-stubs

A static helper moves to the via's type by name, before the instance method that calls it moves through the via; without stubs every call is updated

success
Targetsymbol T:Shop.Order
Arguments
methods["UnitPrice", "Round"]
via"_pricing"
stubfalse
Order.csmodified
1−using System;
2−
31 namespace Shop
42 {
53 public class Order
64 {
75 private readonly Pricing _pricing = new Pricing();
86
97 public int Quantity { get; set; }
108
11− public decimal Total() => Quantity * UnitPrice();
12−
13− private decimal UnitPrice() => Round(_pricing.Base * (1 - _pricing.Discount));
14−
15− private static decimal Round(decimal amount) => Math.Round(amount, 2);
9+ public decimal Total() => Quantity * _pricing.UnitPrice();
1610 }
1711 }
Pricing.csmodified
1+using System;
2+
13 namespace Shop
24 {
35 public class Pricing
46 {
57 public decimal Base { get; set; }
68
79 public decimal Discount { get; set; }
10+
11+ internal static decimal Round(decimal amount) => Math.Round(amount, 2);
12+
13+ internal decimal UnitPrice() => Round(Base * (1 - Discount));
814 }
915 }