Pull Up Constructor Body
Before you begin: Constructors, inheritance, and initialization order.
Pull Up Constructor Body moves duplicated initialization from subclass constructors into their common base constructor. Each subclass supplies the necessary arguments instead of repeating the same setup statements. 1
Original version: every subtype trims its name
A small application has editor and viewer objects. Both inherit a name field and normalize incoming names by trimming surrounding whitespace. Inputs are non-null strings.
public abstract class User
{
public string Name { get; protected set; } = "";
}
public sealed class Editor : User
{
public Editor(string name) { Name = name.Trim(); }
}
public sealed class Viewer : User
{
public Viewer(string name) { Name = name.Trim(); }
}class User:
pass
class Editor(User):
def __init__(self, name):
self.name = name.strip()
class Viewer(User):
def __init__(self, name):
self.name = name.strip()The normalization belongs to all users, but every subclass is responsible for remembering it.
Refactored version: initialize the shared state once
public abstract class User
{
public string Name { get; }
protected User(string name) { Name = name.Trim(); }
}
public sealed class Editor : User
{
public Editor(string name) : base(name) { }
}
public sealed class Viewer : User
{
public Viewer(string name) : base(name) { }
}The base(name) call establishes Name before the subclass constructor body runs. The property no longer needs a protected setter because the base constructor initializes it directly.
class User:
def __init__(self, name):
self.name = name.strip()
class Editor(User):
def __init__(self, name):
super().__init__(name)
class Viewer(User):
def __init__(self, name):
super().__init__(name)super().__init__ performs the shared setup. Any subtype-specific initialization can then follow it explicitly.
Construction order is not cosmetic
Move only work that the base constructor can perform with the values available at that point. If initialization depends on a subclass field that has not been set yet, pulling it upward can introduce a defect.
Avoid invoking overridable methods from a base constructor unless the design explicitly accounts for partially initialized subclasses. Shared validation also needs consistent exception behavior: centralizing it should not silently change which invalid input is reported first.
The shown constructors still turn " Mira " into "Mira". Their visible normalization is unchanged. The migration narrows protected mutation in the C# hierarchy, so unshown subclasses relying on that setter would need review before adopting this exact implementation.