RefactorMCP catalog

Extract Superclass

Gives a class a new base class and moves some of its fields and methods up into it, so that other classes can later share them by deriving from it too.

Recipe

  1. create-type the superclass, deriving from the class's current base class if it has one: { "name": "Employee", "file": "Employee.cs", "baseType": "Person" }.
  2. change-base-type the class to it: "target": { "symbol": "T:Staff.Manager" }, "arguments": { "to": "Employee" }.
  3. pull-up-field each field: "target": { "symbol": "F:Staff.Manager._name" }.
  4. pull-up-method each method: "target": { "symbol": "M:Staff.Manager.Badge" }.

The superclass takes over the old base class, so every inherited member stays inherited and Change Base Type has nothing to refuse. Fields go up before methods, so a method using a pulled-up field finds it in the superclass.

Arguments

ArgumentRequiredMeaning
nameyesThe superclass's name
membersnoThe fields and methods to pull up, by name; a method name pulls up every overload. Without it the superclass starts empty
filenoThe superclass's file, relative to the solution; defaults to <name>.cs beside the class

The target is the class, by symbol: "target": { "symbol": "T:Staff.Manager" }.

Precondition

  • The target is a class, not static and not a record.
  • No type of the superclass's name exists in the class's namespace.
  • Every named member exists and is a field or ordinary method.
  • Each member meets the precondition of Pull Up Field or Pull Up Method: it uses nothing that stays behind in the class, and the result compiles.

Transformation

  • The superclass is public, in the class's namespace, and derives from the class's old base class, written as the class wrote it.
  • The class derives from the superclass instead, keeping its interfaces.
  • Each member moves up as its primitive moves it: a private member becomes protected, and documentation comments go with it.

Preserved

  • Every use of the class and its members, which now reach the pulled-up members by inheritance.
  • The class's interfaces and the comments around its declaration.

Limitations

  • Only one class is given the superclass. Another class with the same members can be moved under it with Change Base Type, after which pulling a member up from either removes the identical copy from the other.
  • Properties are not pulled up, because there is no Pull Up Property.
  • A generic class's type parameters are not given to the superclass.

Error codes

CodeMeaning
not-a-classthe target is a struct, interface, record or static class
member-not-founda named member does not exist in the class
member-not-movablea named member is not a field or ordinary method
type-already-existsa type of the superclass's name exists (step 1)
uses-subclass-membersa member uses something that stays in the class
member-exists-in-basethe old base class already has a member of that name
breaks-compilationpulling a member up would not compile

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.

#field-and-method

Creates an Employee base class for Manager and pulls the name and badge up into it in one call, with the same result as the recipe

success
Targetsymbol T:Staff.Manager
Arguments
name"Employee"
members["Badge", "_name"]
file"Employee.cs"
Employee.csadded
1+namespace Staff
2+{
3+ public class Employee
4+ {
5+ protected string _name;
6+
7+ /// <summary>The name as printed on a badge.</summary>
8+ public string Badge()
9+ {
10+ return "Name: " + _name;
11+ }
12+ }
13+}
Manager.csmodified
11 namespace Staff
22 {
3− public class Manager
3+ public class Manager : Employee
44 {
5− private string _name;
6−
75 public Manager(string name)
86 {
97 _name = name;
108 }
119
1210 public decimal Bonus { get; set; }
1311
14− /// <summary>The name as printed on a badge.</summary>
15− public string Badge()
16− {
17− return "Name: " + _name;
18− }
19−
2012 public string Payslip() => Badge() + ", bonus " + Bonus;
2113 }
2214 }
Reception.csunchanged
11 namespace Staff
22 {
33 public class Reception
44 {
55 public string Welcome(Manager manager) => "Welcome, " + manager.Badge();
66 }
77 }

#keeps-base-and-interfaces

The new superclass derives from the class's old base class, the class keeps its interfaces, and a pulled-up method still reaches inherited members

success
Targetsymbol T:Staff.Manager
Arguments
name"Employee"
members["_salary", "Greeting"]
Employee.csadded
1+namespace Staff
2+{
3+ public class Employee : Person
4+ {
5+ protected decimal _salary = 60000m;
6+
7+ public string Greeting() => "Hello, " + Name;
8+ }
9+}
Manager.csmodified
11 namespace Staff
22 {
33 public interface IPayable
44 {
55 decimal Pay();
66 }
77
88 public class Person
99 {
1010 public string Name { get; set; } = "";
1111 }
1212
1313 // Managers are paid monthly.
14− public class Manager : Person, IPayable
14+ public class Manager : Employee, IPayable
1515 {
16− private decimal _salary = 60000m;
17−
1816 public decimal Pay() => _salary / 12;
19−
20− public string Greeting() => "Hello, " + Name;
2117 }
2218 }

#member-not-found-rejected

A named member does not exist in the class

refusal
Targetsymbol T:Staff.Manager
Arguments
name"Employee"
members["_name", "Salary"]
Refusesmember-not-found mentioning “Salary”; every file is left unchanged
Manager.csinput
1 namespace Staff
2 {
3 public class Manager
4 {
5 private string _name;
6
7 public Manager(string name)
8 {
9 _name = name;
10 }
11
12 public decimal Bonus { get; set; }
13
14 /// <summary>The name as printed on a badge.</summary>
15 public string Badge()
16 {
17 return "Name: " + _name;
18 }
19
20 public string Payslip() => Badge() + ", bonus " + Bonus;
21 }
22 }

#not-a-class-rejected

A struct cannot derive from a class

refusal
Targetsymbol T:Staff.Manager
Arguments
name"Employee"
members["Name"]
Refusesnot-a-class; every file is left unchanged
Manager.csinput
1 namespace Staff
2 {
3 public struct Manager
4 {
5 public string Name;
6
7 public string Badge() => "Name: " + Name;
8 }
9 }

#recipe

Creates an Employee base class for Manager and pulls the name and badge up into it, as the recipe of primitive steps

successrecipe
Recipe
  1. create-type
    name"Employee"
    file"Employee.cs"
  2. change-base-type on symbol T:Staff.Manager
    to"Employee"
  3. pull-up-field on symbol F:Staff.Manager._name
  4. pull-up-method on symbol M:Staff.Manager.Badge
Employee.csadded
1+namespace Staff
2+{
3+ public class Employee
4+ {
5+ protected string _name;
6+
7+ /// <summary>The name as printed on a badge.</summary>
8+ public string Badge()
9+ {
10+ return "Name: " + _name;
11+ }
12+ }
13+}
Manager.csmodified
11 namespace Staff
22 {
3− public class Manager
3+ public class Manager : Employee
44 {
5− private string _name;
6−
75 public Manager(string name)
86 {
97 _name = name;
108 }
119
1210 public decimal Bonus { get; set; }
1311
14− /// <summary>The name as printed on a badge.</summary>
15− public string Badge()
16− {
17− return "Name: " + _name;
18− }
19−
2012 public string Payslip() => Badge() + ", bonus " + Bonus;
2113 }
2214 }
Reception.csunchanged
11 namespace Staff
22 {
33 public class Reception
44 {
55 public string Welcome(Manager manager) => "Welcome, " + manager.Badge();
66 }
77 }

#type-already-exists-rejected

A type with the superclass's name already exists

refusal
Targetsymbol T:Staff.Manager
Arguments
name"Employee"
members["_name", "Badge"]
Refusestype-already-exists mentioning “step 1 (create-type)”; every file is left unchanged
Employee.csinput
1 namespace Staff
2 {
3 public class Employee
4 {
5 }
6 }
Manager.csinput
1 namespace Staff
2 {
3 public class Manager
4 {
5 private string _name;
6
7 public Manager(string name)
8 {
9 _name = name;
10 }
11
12 public decimal Bonus { get; set; }
13
14 /// <summary>The name as printed on a badge.</summary>
15 public string Badge()
16 {
17 return "Name: " + _name;
18 }
19
20 public string Payslip() => Badge() + ", bonus " + Bonus;
21 }
22 }

#uses-subclass-members-rejected

Payslip uses Bonus, which stays in Manager, so pulling it up refuses after the superclass was created and made the base, and every file is put back

refusal
Targetsymbol T:Staff.Manager
Arguments
name"Employee"
members["_name", "Payslip"]
Refusesuses-subclass-members mentioning “step 4 (pull-up-method)”; every file is left unchanged
Manager.csinput
1 namespace Staff
2 {
3 public class Manager
4 {
5 private string _name;
6
7 public Manager(string name)
8 {
9 _name = name;
10 }
11
12 public decimal Bonus { get; set; }
13
14 /// <summary>The name as printed on a badge.</summary>
15 public string Badge()
16 {
17 return "Name: " + _name;
18 }
19
20 public string Payslip() => Badge() + ", bonus " + Bonus;
21 }
22 }