RefactorMCP catalog

Pull Up Constructor Body

Moves the statements at the start of a subclass constructor that only set up the base class into a base constructor, and makes the subclass constructor chain to it.

Arguments

None. The target is the constructor, by symbol: "target": { "symbol": "M:Shop.Manager.#ctor(System.String,System.Int32)" }.

Precondition

  • The class has a base class, declared in the solution.
  • The constructor does not already pass arguments to a base constructor or chain to another of its own constructors. An empty base() is replaced.
  • Its first statement can move. A statement can move when it assigns a field or non-virtual property the base class has, and reads only the constructor's parameters, static members, and fields and non-virtual properties the base class has. A statement that calls an instance method of the class could reach an override before the subclass has run its own initialisation, so it stays.
  • When the base class already has a constructor taking the same parameter types, it does exactly what the moved statements do, and runs the base class's parameterless constructor first when the subclass did.
  • The result compiles.

Transformation

  • The leading run of statements that can move is taken, stopping at the first that cannot, so the order in which things are assigned is kept.
  • The base class gains a protected constructor taking the parameters those statements read, in the subclass constructor's order and with their names, after its fields and constructors. When the base class already has a constructor that does the same, it is reused.
  • When the subclass constructor ran a base constructor with statements of its own, the parameterless one, the new base constructor chains to it with this() so those statements still run first.
  • The subclass constructor calls base(...) with those parameters, on its own line when its body's brace is on its own line.
  • When the base class had only its implicit parameterless constructor and something else relied on it (another subclass, another constructor, or code creating the base class), that constructor is declared explicitly, public or protected for an abstract class, as it was in effect.
  • Comments inside the moved statements move with them.

Preserved

  • The values every field and property holds when construction finishes.
  • The subclass's remaining statements and their comments.

Limitations

  • Only the one constructor is changed; other subclasses with the same statements can be pulled up in turn, and reuse the base constructor.
  • Only assignments move. A statement that declares a local, or calls a method for its effect, ends the run even when it would be safe to move.

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
already-chainsthe constructor already passes arguments to base(...) or calls this(...)
nothing-to-pull-upthe constructor's first statement cannot move to the base class
base-constructor-existsthe base class has a constructor with the same parameters that does something else
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.

#already-chains

Refuses a constructor that already passes arguments to a base constructor

refusal
Targetsymbol M:Shop.Manager.#ctor(System.String,System.Int32)
Refusesalready-chains; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public abstract class Employee
4 {
5 protected string _name;
6 protected int _grade;
7
8 protected Employee(string name)
9 {
10 _name = name;
11 }
12 }
13
14 public class Manager : Employee
15 {
16 public Manager(string name, int grade)
17 : base(name)
18 {
19 _grade = grade;
20 }
21 }
22 }

#base-constructor-exists

Refuses when the base class has a constructor with the same parameters that does something else

refusal
Targetsymbol M:Shop.Manager.#ctor(System.String)
Refusesbase-constructor-exists; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public abstract class Employee
4 {
5 protected string _name;
6
7 protected Employee()
8 {
9 }
10
11 protected Employee(string name)
12 {
13 _name = name.ToUpperInvariant();
14 }
15 }
16
17 public class Manager : Employee
18 {
19 public Manager(string name)
20 {
21 _name = name;
22 }
23 }
24 }

#base-constructor-in-another-file

Moves the statements that only set the base class's fields into a new base constructor, and chains to it

success
Targetsymbol M:Shop.Manager.#ctor(System.String,System.String,System.Int32)
Employee.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 protected string _name;
66 protected string _id;
77
8+ protected Employee(string name, string id)
9+ {
10+ _name = name;
11+ _id = id;
12+ }
13+
814 public string Describe() => _id + " " + _name;
915 }
1016 }
Manager.csmodified
11 namespace Shop
22 {
33 public class Manager : Employee
44 {
55 private readonly int _grade;
66
77 public Manager(string name, string id, int grade)
8+ : base(name, id)
89 {
9− _name = name;
10− _id = id;
1110 _grade = grade;
1211 }
1312
1413 public int Grade => _grade;
1514 }
1615 }

#base-not-in-source

Refuses when the base class is not declared in the solution

refusal
Targetsymbol M:Shop.OrderException.#ctor(System.Int32)
Refusesbase-not-in-source; every file is left unchanged
OrderException.csinput
1 using System;
2
3 namespace Shop
4 {
5 public class OrderException : Exception
6 {
7 public int OrderId;
8
9 public OrderException(int orderId)
10 {
11 OrderId = orderId;
12 }
13 }
14 }

#chains-to-identical-base-constructor

Chains to a base constructor that already does exactly what the statements do

success
Targetsymbol M:Shop.Engineer.#ctor(System.String,System.Int32)
People.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 protected string _name;
66
77 protected Employee()
88 {
99 }
1010
1111 protected Employee(string name)
1212 {
1313 _name = name;
1414 }
1515 }
1616
1717 public class Engineer : Employee
1818 {
1919 private readonly int _level;
2020
2121 public Engineer(string name, int level)
22+ : base(name)
2223 {
23− _name = name;
2424 _level = level;
2525 }
2626 }
2727 }

#keeps-comments

Moves the comments inside the moved statements with them and leaves the rest in place

success
Targetsymbol M:Shop.Manager.#ctor(System.String,System.Int32)
People.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 protected string _name;
6+
7+ protected Employee(string name)
8+ {
9+ // Every employee is named.
10+ _name = name;
11+ }
612 }
713
814 public class Manager : Employee
915 {
1016 private readonly int _grade;
1117
1218 public Manager(string name, int grade)
19+ : base(name)
1320 {
14− // Every employee is named.
15− _name = name;
16−
1721 // Only managers are graded.
1822 _grade = grade;
1923 }
2024 }
2125 }

#keeps-parameterless-for-other-subclasses

Declares the parameterless constructor the other subclasses relied on, since a new base constructor hides the implicit one

success
Targetsymbol M:Shop.Manager.#ctor(System.String)
People.csmodified
11 namespace Shop
22 {
33 public class Employee
44 {
55 public string Name;
6+
7+ public Employee()
8+ {
9+ }
10+
11+ protected Employee(string name)
12+ {
13+ Name = name;
14+ }
615 }
716
817 public class Manager : Employee
918 {
1019 public Manager(string name)
20+ : base(name)
1121 {
12− Name = name;
1322 }
1423 }
1524
1625 public class Engineer : Employee
1726 {
1827 }
1928 }

#keeps-running-parameterless-body

Chains the new base constructor to the parameterless one the subclass ran before, so its statements still run first

success
Targetsymbol M:Shop.Manager.#ctor(System.String,System.Int32)
People.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 protected bool _active;
66 protected string _name;
77
88 protected Employee()
99 {
1010 _active = true;
1111 }
12+
13+ protected Employee(string name)
14+ : this()
15+ {
16+ _name = name;
17+ }
1218 }
1319
1420 public class Manager : Employee
1521 {
1622 private readonly int _grade;
1723
1824 public Manager(string name, int grade)
25+ : base(name)
1926 {
20− _name = name;
2127 _grade = grade;
2228 }
2329 }
2430 }

#no-base-class

Refuses when the class has no base class

refusal
Targetsymbol M:Shop.Manager.#ctor(System.String)
Refusesno-base-class; every file is left unchanged
Manager.csinput
1 namespace Shop
2 {
3 public class Manager
4 {
5 private readonly string _name;
6
7 public Manager(string name)
8 {
9 _name = name;
10 }
11
12 public string Name => _name;
13 }
14 }

#nothing-to-pull-up

Refuses when the first statement already needs the subclass

refusal
Targetsymbol M:Shop.Manager.#ctor(System.String,System.Int32)
Refusesnothing-to-pull-up; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public abstract class Employee
4 {
5 protected string _name;
6 }
7
8 public class Manager : Employee
9 {
10 private readonly int _grade;
11
12 public Manager(string name, int grade)
13 {
14 _grade = grade;
15 _name = name;
16 }
17 }
18 }

#nullable-fields-initialised

Initialises the base class's non-nullable field in its own constructor

success
Targetsymbol M:Shop.Manager.#ctor(System.String,System.String)
Projectnullable enable
People.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 protected string _name;
66 protected string? _nickname;
7+
8+ protected Employee(string name, string? nickname)
9+ {
10+ _name = name;
11+ _nickname = nickname;
12+ }
713 }
814
915 public class Manager : Employee
1016 {
1117 public Manager(string name, string? nickname)
18+ : base(name, nickname)
1219 {
13− _name = name;
14− _nickname = nickname;
1520 }
1621 }
1722 }

#stops-at-first-subclass-statement

Moves only the leading statements, stopping at the first that touches the subclass, so the order of assignments is kept

success
Targetsymbol M:Shop.Manager.#ctor(System.String,System.Int32,System.String)
People.csmodified
11 namespace Shop
22 {
33 public abstract class Employee
44 {
55 public string Name { get; protected set; }
66
77 protected string _id;
8+
9+ protected Employee(string name)
10+ {
11+ Name = name.Trim();
12+ }
813 }
914
1015 public class Manager : Employee
1116 {
1217 private readonly int _grade;
1318
1419 public Manager(string name, int grade, string id)
20+ : base(name)
1521 {
16− Name = name.Trim();
1722 _grade = grade;
1823 _id = id;
1924 }
2025
2126 public int Grade => _grade;
2227 }
2328 }