RefactorMCP catalog

Pull Up Method

Moves a method from a class into its base class, or declares it abstract in the base class and makes the subclasses' versions override it.

Arguments

ArgumentRequiredMeaning
abstractnotrue to declare the method abstract in the base class and leave the bodies in the subclasses

The target is the method, by symbol, which tells overloads apart: "target": { "symbol": "M:Shop.Manager.Pay(System.Decimal)" }.

Precondition

  • The class has a base class, declared in the solution.
  • The method uses nothing the base class cannot see: no other member or nested type of the subclass, and no type parameter of the subclass the base class does not share. With abstract, only the signature has to meet this.
  • Neither the base class nor a class above it has a method with the same parameters, or a non-method member, of that name.
  • Every other subclass that declares a method with the same name and parameters is a direct subclass, and declares the same method with the same body. With abstract, the body may differ, but every other direct subclass that is not abstract must declare the method.
  • With abstract, the base class is abstract.
  • The result compiles.

Transformation

  • The method goes at the end of the base class, with its documentation and comments. A private method becomes protected.
  • The subclass's type parameters are written as the base class's, and the base class's file gains the usings the method needs.
  • Identical methods in the other subclasses are removed.
  • With abstract, the base class gains an abstract declaration with the method's signature and accessibility, and the method in the subclass and each sibling's matching method become overrides. The body and its documentation stay where they are.

Preserved

  • Every call to the method, which now binds to the inherited method or the override.
  • The other overloads, which stay in the subclass.

Limitations

  • A call through base. inside the moved method keeps its text, so it now reaches the base class's own base; the compile check refuses it only when that no longer binds.
  • Usings the subclass's file no longer needs are left in place.

Error codes

CodeMeaning
no-base-classthe class has no base class other than object
base-not-in-sourcethe base class is not declared in the solution
member-exists-in-basethe base class or a class above it already has the method
conflicts-with-siblinganother subclass has a different method with that signature
uses-subclass-membersthe method uses something only the subclass has
base-not-abstractabstract was asked for but the base class is not abstract
sibling-lacks-implementationwith abstract, another subclass does not declare 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-needs-abstract-base

Refuses to declare an abstract method in a class that is not abstract

refusal
Targetsymbol M:Shop.Manager.Bonus
Arguments
abstracttrue
Refusesbase-not-abstract mentioning “Employee”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 }
6
7 public class Manager : Employee
8 {
9 public decimal Bonus() => 100;
10 }
11 }

#abstract-sibling-lacks-method

Refuses to declare an abstract method another subclass does not implement

refusal
Targetsymbol M:Shop.Manager.Bonus
Arguments
abstracttrue
Refusessibling-lacks-implementation mentioning “Engineer”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public abstract class Employee
4 {
5 }
6
7 public class Manager : Employee
8 {
9 public decimal Bonus() => 100;
10 }
11
12 public class Engineer : Employee
13 {
14 }
15 }

#as-abstract

Declares the method abstract in the base class and makes each subclass's version an override

success
Targetsymbol M:Shop.Manager.Bonus
Arguments
abstracttrue
People.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 public decimal Salary;
6+
7+ public abstract decimal Bonus();
68 }
79
810 public class Manager : Employee
911 {
1012 private int _grade = 3;
1113
12− public decimal Bonus() => Salary * _grade / 10;
14+ public override decimal Bonus() => Salary * _grade / 10;
1315 }
1416
1517 public class Engineer : Employee
1618 {
17− public decimal Bonus()
19+ public override decimal Bonus()
1820 {
1921 return 100;
2022 }
2123 }
2224 }

#conflicts-with-sibling

Refuses when another subclass has the same method with a different body, which would hide the moved one

refusal
Targetsymbol M:Shop.Manager.Title
Refusesconflicts-with-sibling mentioning “Engineer”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 }
6
7 public class Manager : Employee
8 {
9 public string Title() => "Manager";
10 }
11
12 public class Engineer : Employee
13 {
14 public string Title() => "Engineer";
15 }
16 }

#generic-base

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

success
Targetsymbol M:Shop.Repository`1.First
Repository.csmodified
11 using System.Linq;
22
33 namespace Shop
44 {
55 public class Repository<T> : Store<T>
66 {
7− public T First() => Items.First();
87 }
98 }
Store.csmodified
11 using System.Collections.Generic;
2+using System.Linq;
23
34 namespace Shop
45 {
56 public abstract class Store<TItem>
67 {
78 protected readonly List<TItem> Items = new List<TItem>();
9+
10+ public TItem First() => Items.First();
811 }
912 }

#keeps-comments-with-nullable

Moves the documentation comment and body comments with the method, keeping nullable annotations

success
Targetsymbol M:Shop.Manager.FindNickname(System.String)
Projectnullable enable
People.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
55 public class Employee
66 {
77 protected Dictionary<string, string> Nicknames = new Dictionary<string, string>();
8− }
98
10− public class Manager : Employee
11− {
129 /// <summary>The nickname for a name, if there is one.</summary>
1310 public string? FindNickname(string name)
1411 {
1512 // Nicknames are optional.
1613 return Nicknames.TryGetValue(name, out var nickname) ? nickname : null;
1714 }
15+ }
1816
17+ public class Manager : Employee
18+ {
1919 // Grades run from one to five.
2020 public int Grade;
2121 }
2222 }

#member-exists-in-base

Refuses when the base class already declares the method

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

#method-to-base-in-another-file

Moves a method that only uses inherited members into the base class in another file

success
Targetsymbol M:Shop.Manager.Describe
Employee.csmodified
11 namespace Shop
22 {
33 public class Employee
44 {
55 public string Name;
66
77 public decimal Salary;
8+
9+ public string Describe()
10+ {
11+ return Name + " earns " + Salary;
12+ }
813 }
914 }
Manager.csmodified
11 namespace Shop
22 {
33 public class Manager : Employee
44 {
55 public int Grade;
66
7− public string Describe()
8− {
9− return Name + " earns " + Salary;
10− }
11−
127 public bool IsSenior() => Grade > 2;
138 }
149 }
Report.csunchanged
11 namespace Shop
22 {
33 public static class Report
44 {
55 public static string Line(Manager manager) => manager.Describe();
66 }
77 }

#no-base-class

Refuses when the class has no base class

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

#one-overload

Moves one overload and leaves the other in the subclass

success
Targetsymbol M:Shop.Manager.Pay(System.Decimal)
People.csmodified
11 namespace Shop
22 {
33 public class Employee
44 {
55 public decimal Paid;
6+
7+ public void Pay(decimal amount)
8+ {
9+ Paid += amount;
10+ }
611 }
712
813 public class Manager : Employee
914 {
1015 public int Grade;
1116
1217 public void Pay() => Pay(Grade * 1000m);
13−
14− public void Pay(decimal amount)
15− {
16− Paid += amount;
17− }
1818 }
1919 }

#private-method-becomes-protected

Widens a private method to protected so the subclass can still call it

success
Targetsymbol M:Shop.Manager.Tax(System.Decimal)
People.csmodified
11 namespace Shop
22 {
33 public class Employee
44 {
55 public decimal Salary;
6+
7+ protected decimal Tax(decimal amount) => amount * 0.2m;
68 }
79
810 public class Manager : Employee
911 {
1012 public decimal Net() => Salary - Tax(Salary);
11−
12− private decimal Tax(decimal amount) => amount * 0.2m;
1313 }
1414 }

#removes-identical-sibling-methods

Removes the same method from the other subclasses, which now inherit it

success
Targetsymbol M:Shop.Engineer.AnnualCost
Employee.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 public decimal MonthlyCost;
6+
7+ public decimal AnnualCost()
8+ {
9+ return MonthlyCost * 12;
10+ }
611 }
712 }
Engineer.csmodified
11 namespace Shop
22 {
33 public class Engineer : Employee
44 {
5− public decimal AnnualCost()
6− {
7− return MonthlyCost * 12;
8− }
95 }
106 }
Salesman.csmodified
11 namespace Shop
22 {
33 public class Salesman : Employee
44 {
55 public decimal Commission;
6−
7− public decimal AnnualCost()
8− {
9− return MonthlyCost * 12;
10− }
116 }
127 }

#uses-subclass-members

Refuses when the method uses a member only the subclass has

refusal
Targetsymbol M:Shop.Manager.Bonus
Refusesuses-subclass-members mentioning “Grade”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 public decimal Salary;
6 }
7
8 public class Manager : Employee
9 {
10 public int Grade;
11
12 public decimal Bonus() => Salary * Grade / 10;
13 }
14 }