RefactorMCP catalog

Push Down Method

Moves a method from a class into the direct subclasses that need it: those whose code calls it, or whose callers reach it through a reference of the subclass's type. When nothing calls it, every direct subclass gets a copy. An abstract method is removed instead, and each subclass's implementation stays as an ordinary method.

Arguments

None. The target is the method, by symbol, which tells overloads apart: "target": { "symbol": "M:Shop.Employee.Report(System.Int32)" }.

Precondition

  • The class has at least one subclass declared in the solution.
  • The method does not override one from further up: callers reach an override through the type that declared the original, without naming the class.
  • A virtual method is not overridden anywhere, since the subclasses would then disagree about what the moved method does.
  • The class does not call the method itself, and no code calls it through a reference of the class's type or of a type above it.
  • The method uses nothing private to the class.
  • No receiving subclass already has a method with the same parameters, or a non-method member, of that name.
  • The result compiles.

Transformation

  • Each receiving subclass gets the method at its end, with its documentation, comments and modifiers.
  • In a sealed subclass a protected method becomes private and a virtual one stops being virtual.
  • The class's type parameters are written as the subclass's type arguments, and each subclass's file gains the usings the method needs.
  • For an abstract method, the declaration is removed and each override loses its override (and sealed) modifier, becoming virtual when a class further down overrides it in turn.

Preserved

  • Every call to the method, which now binds to the subclass's copy.
  • The other overloads, which stay in the class.

Limitations

  • Each receiving subclass gets its own copy of the body; later changes to one do not reach the others.
  • An interface the class implements through the method is refused by the compile check rather than moved to the subclasses.
  • Usings the class's file no longer needs are left in place.

Error codes

CodeMeaning
no-subclassesno class in the solution derives from the method's class
method-is-overridethe method overrides one from a class further up
method-is-overriddenthe method is virtual and a subclass overrides it
used-by-basethe method's class calls it itself
used-through-basecode calls the method through a reference of the method's class
uses-base-private-membersthe method uses something private to its class
member-exists-in-subclassa receiving subclass already has the method
breaks-compilationthe result would not compile

Cases

·

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

#abstract-method

Removes an abstract declaration, leaving each subclass's implementation as an ordinary method

success
Targetsymbol M:Shop.Employee.Commission
People.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 public decimal Salary;
6−
7− public abstract decimal Commission();
86 }
97
108 public class Salesman : Employee
119 {
1210 public decimal Sales;
1311
14− public override decimal Commission() => Sales / 10;
12+ public decimal Commission() => Sales / 10;
1513 }
1614
1715 public class Engineer : Employee
1816 {
19− public override decimal Commission()
17+ public decimal Commission()
2018 {
2119 return 0;
2220 }
2321 }
2422 }
Payroll.csunchanged
11 namespace Shop
22 {
33 public static class Payroll
44 {
55 public static decimal Total(Salesman salesman) => salesman.Salary + salesman.Commission();
66 }
77 }

#generic-base

Writes the base class's type parameter as the subclass's type argument in the moved method

success
Targetsymbol M:Shop.Store`1.Describe(`0)
OrderStore.csmodified
11 namespace Shop
22 {
33 public class Order
44 {
55 }
66
77 public class OrderStore : Store<Order>
88 {
99 public string First(Order order) => Describe(order);
10+
11+ public string Describe(Order item) => "Item " + item;
1012 }
1113 }
Store.csmodified
11 namespace Shop
22 {
33 public class Store<TItem>
44 {
5− public string Describe(TItem item) => "Item " + item;
65 }
76 }

#keeps-comments-with-nullable

Moves the method's documentation and body comments with it, keeping nullable annotations

success
Targetsymbol M:Shop.Employee.FindTerritory(System.String)
Projectnullable enable
People.csmodified
11 namespace Shop
22 {
33 public class Employee
44 {
55 // Everyone has a name.
66 public string Name = "";
7−
8− /// <summary>The territory for a region, if one is assigned.</summary>
9− public string? FindTerritory(string region)
10− {
11− // Unassigned regions have no territory.
12− return region == "north" ? "N1" : null;
13− }
147
158 // Pay is monthly.
169 public decimal Salary;
1710 }
1811
1912 public class Salesman : Employee
2013 {
2114 public string Territory(string region) => FindTerritory(region) ?? Name;
15+
16+ /// <summary>The territory for a region, if one is assigned.</summary>
17+ public string? FindTerritory(string region)
18+ {
19+ // Unassigned regions have no territory.
20+ return region == "north" ? "N1" : null;
21+ }
2222 }
2323 }

#member-exists-in-subclass

Refuses when a subclass that would receive the method already has one with the same parameters

refusal
Targetsymbol M:Shop.Employee.Describe
Refusesmember-exists-in-subclass mentioning “Salesman”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 public string Describe() => "employee";
6 }
7
8 public class Salesman : Employee
9 {
10 public new string Describe() => "salesman";
11 }
12 }

#no-subclasses

Refuses when no class derives from the method's class

refusal
Targetsymbol M:Shop.Employee.Describe
Refusesno-subclasses; every file is left unchanged
Employee.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 public string Describe() => "employee";
6 }
7 }

#one-overload

Moves one overload and leaves the other in the base class

success
Targetsymbol M:Shop.Employee.Report(System.Int32)
People.csmodified
11 namespace Shop
22 {
33 public class Employee
44 {
55 public string Report() => "all";
6−
7− public string Report(int year) => "year " + year;
86 }
97
108 public class Salesman : Employee
119 {
1210 public string Annual() => Report(2024) + Report();
11+
12+ public string Report(int year) => "year " + year;
1313 }
1414 }

#overridden

Refuses a virtual method a subclass overrides, since the subclasses would disagree about what it does

refusal
Targetsymbol M:Shop.Employee.Rate
Refusesmethod-is-overridden mentioning “Manager”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 public virtual decimal Rate() => 0.1m;
6 }
7
8 public class Salesman : Employee
9 {
10 }
11
12 public class Manager : Employee
13 {
14 public override decimal Rate() => 0.2m;
15 }
16 }

#overrides-base-member

Refuses a method that overrides one from further up, which callers reach without naming the class

refusal
Targetsymbol M:Shop.Employee.ToString
Refusesmethod-is-override mentioning “ToString”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 public string Name;
6
7 public override string ToString() => Name;
8 }
9
10 public class Salesman : Employee
11 {
12 }
13 }

#sealed-subclass-becomes-private

Narrows a protected virtual method to a private one in a sealed subclass

success
Targetsymbol M:Shop.Employee.Rate
People.csmodified
11 namespace Shop
22 {
33 public class Employee
44 {
5− protected virtual decimal Rate() => 0.1m;
65 }
76
87 public sealed class Salesman : Employee
98 {
109 public decimal Commission(decimal sales) => sales * Rate();
10+
11+ private decimal Rate() => 0.1m;
1112 }
1213 }

#to-every-subclass-when-unused

Copies a method no code calls into every direct subclass, so it is not lost

success
Targetsymbol M:Shop.Employee.Describe
People.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 public string Name;
6−
7− public string Describe() => "Employee " + Name;
86 }
97
108 public class Salesman : Employee
119 {
10+ public string Describe() => "Employee " + Name;
1211 }
1312
1413 public class Engineer : Employee
1514 {
1615 public int Level;
16+
17+ public string Describe() => "Employee " + Name;
1718 }
1819 }

#to-subclass-that-uses-it

Moves a method into the one subclass whose code and callers use it

success
Targetsymbol M:Shop.Employee.QuotaReport
Employee.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 public string Name;
6−
7− public string QuotaReport()
8− {
9− return Name + " has a quota";
10− }
116 }
127 }
Salesman.csmodified
11 namespace Shop
22 {
33 public class Salesman : Employee
44 {
55 public decimal Sales;
6+
7+ public string QuotaReport()
8+ {
9+ return Name + " has a quota";
10+ }
611 }
712 }
Engineer.csunchanged
11 namespace Shop
22 {
33 public class Engineer : Employee
44 {
55 public int Level;
66 }
77 }
Reports.csunchanged
11 namespace Shop
22 {
33 public static class Reports
44 {
55 public static string For(Salesman salesman) => salesman.QuotaReport();
66 }
77 }

#used-by-base

Refuses when the base class itself calls the method

refusal
Targetsymbol M:Shop.Employee.Rate
Refusesused-by-base mentioning “Employee”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 public decimal Salary;
6
7 protected decimal Rate() => 0.1m;
8
9 public decimal Bonus() => Salary * Rate();
10 }
11
12 public class Salesman : Employee
13 {
14 public decimal Commission(decimal sales) => sales * Rate();
15 }
16 }

#used-through-base

Refuses when code elsewhere calls the method through a base-typed reference

refusal
Targetsymbol M:Shop.Employee.Describe
Refusesused-through-base mentioning “Directory.cs”; every file is left unchanged
Directory.csinput
1 using System.Collections.Generic;
2 using System.Linq;
3
4 namespace Shop
5 {
6 public static class Directory
7 {
8 public static List<string> Describe(List<Employee> staff) => staff.Select(e => e.Describe()).ToList();
9 }
10 }
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 public string Describe() => "employee";
6 }
7
8 public class Salesman : Employee
9 {
10 }
11 }

#uses-base-private-members

Refuses when the method uses a member private to the base class

refusal
Targetsymbol M:Shop.Employee.Bonus(System.Decimal)
Refusesuses-base-private-members mentioning “_rate”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 private decimal _rate = 0.1m;
6
7 public decimal Bonus(decimal salary) => salary * _rate;
8 }
9
10 public class Salesman : Employee
11 {
12 public decimal Paid(decimal salary) => salary + Bonus(salary);
13 }
14 }