For a class that derives from another and also holds a new instance of it in a private field: uses of the field's members become uses of the members the class inherits, and the field is removed, so the instance itself takes the field's place. Replace Delegation with Inheritance uses it once the class derives from the field's class.
Arguments
Argument Required Meaning fieldyes The private field holding an instance of the base class
The target is the class, by symbol: "target": { "symbol": "T:Staff.Employee" }.
Precondition
The target is a class deriving from a class other than object. The field is a private instance field of exactly the base class, type arguments included, initialised with a new instance created without arguments or an object initializer. The class's constructors pass no arguments to the base class, so the instance's base class part and the field's object start out alike. Neither the class nor a subclass overrides a member of the base class, which the base class would call where the field's object would not. The field is never assigned after its initialiser, and every use of it is _person.Member, _person[...] or the same with this., so no code holds the field's object itself. Nothing uses the base class part yet: no code uses an inherited member through the class, inside or outside it, and no code converts an instance of the class to the base class or an interface the base class implements.
Each _person.M becomes M where that reaches the same member, this.M where a local or parameter named M would otherwise be reached, and base.M where a member of the class hides the inherited one, including inside that member (get => _person.Name; becomes get => base.Name;). _items[i] becomes this[i] or base[i] likewise. The field is removed with the comments above it; directives such as #region stay.
Preserved
The behaviour of every member: the base class part now holds what the field held, since both started alike and only the field was ever used. Every member the class declares, and every caller of them.
Limitations
Members of object the base class overrides, such as ToString, now see the state the field held; code calling them on the class is not checked. A field whose object needs constructor arguments is refused rather than turned into a base(...) call.
Error codes
Code Meaning not-a-classthe target is not a class no-base-classthe class derives only from object field-not-foundthe class has no field of that name field-type-not-basethe field's type is not the base class field-not-privatethe field is not a private instance field not-created-by-fieldthe field is not initialised with a new instance of the base class made without arguments base-constructed-with-argumentsa constructor passes arguments to the base class overrides-base-memberthe class or a subclass overrides a member of the base class field-assignedthe field is assigned after its initialiser field-escapesthe field is used as a value rather than through its members base-members-in-usecode already uses an inherited member through the class base-conversion-in-usecode converts the class to its base class or one of the base class's interfaces
Cases
Expand all · Collapse all
Diffs show each file from before to after .
/*[*/ … /*]*/ marks a selection and /*^*/ a caret; the runner removes them before the refactoring runs.
# base-constructed-with-arguments-rejectedRefuses when a constructor passes arguments to the base class, so the instance would not start like the field's object
refusal
Target symbol T:Staff.Employee
Refuses base-constructed-with-arguments; every file is left unchanged
Employee.cs input
1 namespace Staff 2 { 3 public class Employee : Person 4 { 5 private readonly Person _person = new Person (); 6 7 public Employee () : base ("New starter" ) 8 { 9 } 10 11 public string Badge () => "Employee " + _person.Name ; 12 } 13 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public Person () 6 { 7 } 8 9 public Person (string name) => Name = name; 10 11 public string Name { get ; set ; } = "" ; 12 } 13 }
View fixture on GitHub
# base-conversion-in-use-rejectedRefuses when code converts the class to its base class, which would then see what the field held
refusal
Target symbol T:Staff.Employee
Refuses base-conversion-in-use mentioning “Payroll.cs(5,”; every file is left unchanged
Employee.cs input
1 namespace Staff 2 { 3 public class Employee : Person 4 { 5 // The person this employee is. 6 private readonly Person _person = new Person (); 7 8 public decimal Salary { get ; set ; } 9 10 public string Badge () => _person.Greeting ("Employee" ) + " (" + _person.Name + ")" ; 11 } 12 }
Payroll.cs input
1 namespace Staff 2 { 3 public class Payroll 4 { 5 public string Slip (Employee employee) => Describe (employee) + ": " + employee.Badge (); 6 7 private static string Describe (Person person) => person.GetType ().Name ; 8 } 9 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string LastName () => Name .Split (" " )[^1 ]; 8 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 } 11 }
View fixture on GitHub
# base-members-in-use-rejectedRefuses when code already uses an inherited member, which would then see what the field held
refusal
Target symbol T:Staff.Employee
Refuses base-members-in-use mentioning “Payroll.cs(5,”; every file is left unchanged
Employee.cs input
1 namespace Staff 2 { 3 public class Employee : Person 4 { 5 // The person this employee is. 6 private readonly Person _person = new Person (); 7 8 public decimal Salary { get ; set ; } 9 10 public string Badge () => _person.Greeting ("Employee" ) + " (" + _person.Name + ")" ; 11 } 12 }
Payroll.cs input
1 namespace Staff 2 { 3 public class Payroll 4 { 5 public string Slip (Employee employee) => employee.LastName () + ": " + employee.Badge (); 6 } 7 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string LastName () => Name .Split (" " )[^1 ]; 8 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 } 11 }
View fixture on GitHub
# field-assigned-rejectedRefuses a field assigned after its initialiser, which may then hold another object
refusal
Target symbol T:Staff.Employee
Refuses field-assigned mentioning “Employee.cs(11,”; every file is left unchanged
Employee.cs input
1 namespace Staff 2 { 3 public class Employee : Person 4 { 5 private Person _person = new Person (); 6 7 public string Badge () => _person.Greeting ("Employee" ); 8 9 public void Reset () 10 { 11 _person = new Person (); 12 } 13 } 14 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string LastName () => Name .Split (" " )[^1 ]; 8 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 } 11 }
View fixture on GitHub
# field-escapes-rejectedRefuses a field used as an object in its own right, which the instance could not stand in for
refusal
Target symbol T:Staff.Employee
Refuses field-escapes mentioning “Employee.cs(9,”; every file is left unchanged
Employee.cs input
1 namespace Staff 2 { 3 public class Employee : Person 4 { 5 private readonly Person _person = new Person (); 6 7 public string Badge () => _person.Greeting ("Employee" ); 8 9 public Person AsPerson () => _person; 10 } 11 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string LastName () => Name .Split (" " )[^1 ]; 8 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 } 11 }
View fixture on GitHub
# field-in-regionA region around the field stays, and the field's own comment goes with it
success
Target symbol T:Staff.Employee
Employee.cs modified
1 1 namespace Staff 2 2 { 3 3 public class Employee : Person 4 4 { 5 5 #region Delegate 6 − // The person this employee is. 7 − private readonly Person _person = new Person (); 8 6 #endregion 9 7 10 − public string Badge () => _person.Greeting ("Employee" ); 8 + public string Badge () => Greeting ("Employee" ); 11 9 } 12 10 }
Person.cs unchanged
1 1 namespace Staff 2 2 { 3 3 public class Person 4 4 { 5 5 public string Name { get ; set ; } = "" ; 6 6 7 7 public string LastName () => Name .Split (" " )[^1 ]; 8 8 9 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 10 } 11 11 }
View fixture on GitHub
# field-not-found-rejectedRefuses when the class has no field of that name
refusal
Target symbol T:Staff.Employee
Refuses field-not-found mentioning “_self”; every file is left unchanged
Employee.cs input
1 namespace Staff 2 { 3 public class Employee : Person 4 { 5 // The person this employee is. 6 private readonly Person _person = new Person (); 7 8 public decimal Salary { get ; set ; } 9 10 public string Badge () => _person.Greeting ("Employee" ) + " (" + _person.Name + ")" ; 11 } 12 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string LastName () => Name .Split (" " )[^1 ]; 8 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 } 11 }
View fixture on GitHub
# field-not-private-rejectedRefuses a field other code could reach
refusal
Target symbol T:Staff.Employee
Refuses field-not-private; every file is left unchanged
Employee.cs input
1 namespace Staff 2 { 3 public class Employee : Person 4 { 5 // The person this employee is. 6 internal readonly Person _person = new Person (); 7 8 public decimal Salary { get ; set ; } 9 10 public string Badge () => _person.Greeting ("Employee" ) + " (" + _person.Name + ")" ; 11 } 12 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string LastName () => Name .Split (" " )[^1 ]; 8 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 } 11 }
View fixture on GitHub
# field-type-not-base-rejectedRefuses a field whose type is not the base class
refusal
Target symbol T:Staff.Employee
Refuses field-type-not-base; every file is left unchanged
Contact.cs input
1 namespace Staff 2 { 3 public class Contact 4 { 5 public string Email { get ; set ; } = "" ; 6 } 7 }
Employee.cs input
1 namespace Staff 2 { 3 public class Employee : Contact 4 { 5 // The person this employee is. 6 private readonly Person _person = new Person (); 7 8 public decimal Salary { get ; set ; } 9 10 public string Badge () => _person.Greeting ("Employee" ) + " (" + _person.Name + ")" ; 11 } 12 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string LastName () => Name .Split (" " )[^1 ]; 8 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 } 11 }
View fixture on GitHub
# generic-baseWith a constructed generic base, member and element accesses through the field become accesses on the instance
success
Target symbol T:Shop.Inventory
Inventory.cs modified
1 1 using System .Collections .Generic ;2 2 3 3 namespace Shop 4 4 { 5 5 public class Inventory : List <string > 6 6 { 7 − private readonly List <string > _items = new List <string >(); 8 − 9 7 public string Receive (string item) 10 8 { 11 − _items.Add (item); 12 − return _items[0 ] + " of " + _items.Count ; 9 + Add (item); 10 + return this [0 ] + " of " + Count ; 13 11 } 14 12 } 15 13 }
View fixture on GitHub
# hidden-members-use-baseWhere a member of the class hides the inherited one, including inside that member, the use says base; callers elsewhere are untouched
success
Target symbol T:Staff.Employee
Employee.cs modified
1 1 namespace Staff 2 2 { 3 3 public class Employee : Person 4 4 { 5 − // The person this employee is. 6 − private readonly Person _person = new Person (); 7 − 8 5 public decimal Salary { get ; set ; } 9 6 10 7 public string Name 11 8 { 12 − get => _person.Name ; 13 − set => _person.Name = value ; 9 + get => base .Name ; 10 + set => base .Name = value ; 14 11 } 15 12 16 13 public string LastName () 17 14 { 18 − return _person.LastName (); 15 + return base .LastName (); 19 16 } 20 17 21 − public string Badge () => _person.Greeting ("Employee" ) + " (" + this ._person.LastName () + ")" ; 18 + public string Badge () => Greeting ("Employee" ) + " (" + base .LastName () + ")" ; 22 19 } 23 20 }
Payroll.cs unchanged
1 1 namespace Staff 2 2 { 3 3 public class Payroll 4 4 { 5 5 public string Slip (Employee employee) 6 6 { 7 7 employee.Name = "Ann Lee" ; 8 8 return employee.LastName () + ": " + employee.Salary + " " + employee.Badge (); 9 9 } 10 10 } 11 11 }
Person.cs unchanged
1 1 namespace Staff 2 2 { 3 3 public class Person 4 4 { 5 5 public string Name { get ; set ; } = "" ; 6 6 7 7 public string LastName () => Name .Split (" " )[^1 ]; 8 8 9 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 10 } 11 11 }
View fixture on GitHub
# minimalUses of the field's members become uses of the members the class inherits, and the field goes with its comment
success
Target symbol T:Staff.Employee
Employee.cs modified
1 1 namespace Staff 2 2 { 3 3 public class Employee : Person 4 4 { 5 − // The person this employee is. 6 − private readonly Person _person = new Person (); 7 − 8 5 public decimal Salary { get ; set ; } 9 6 10 − public string Badge () => _person.Greeting ("Employee" ) + " (" + _person.Name + ")" ; 7 + public string Badge () => Greeting ("Employee" ) + " (" + Name + ")" ; 11 8 } 12 9 }
Payroll.cs unchanged
1 1 namespace Staff 2 2 { 3 3 public class Payroll 4 4 { 5 5 public string Slip (Employee employee) => employee.Badge () + ": " + employee.Salary ; 6 6 } 7 7 }
Person.cs unchanged
1 1 namespace Staff 2 2 { 3 3 public class Person 4 4 { 5 5 public string Name { get ; set ; } = "" ; 6 6 7 7 public string LastName () => Name .Split (" " )[^1 ]; 8 8 9 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 10 } 11 11 }
View fixture on GitHub
# no-base-class-rejectedRefuses a class that derives only from object
refusal
Target symbol T:Staff.Employee
Refuses no-base-class; every file is left unchanged
Employee.cs input
1 namespace Staff 2 { 3 public class Employee 4 { 5 // The person this employee is. 6 private readonly Person _person = new Person (); 7 8 public decimal Salary { get ; set ; } 9 10 public string Badge () => _person.Greeting ("Employee" ) + " (" + _person.Name + ")" ; 11 } 12 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string LastName () => Name .Split (" " )[^1 ]; 8 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 } 11 }
View fixture on GitHub
# not-a-class-rejectedRefuses a struct, which has no base class
refusal
Target symbol T:Staff.Badge
Refuses not-a-class; every file is left unchanged
Badge.cs input
1 namespace Staff 2 { 3 public struct Badge 4 { 5 private readonly int _number; 6 7 public Badge (int number) => _number = number; 8 9 public int Number => _number; 10 } 11 }
View fixture on GitHub
# not-created-by-field-rejectedRefuses a field whose object is set up differently from a new instance, here by an object initializer
refusal
Target symbol T:Staff.Employee
Refuses not-created-by-field; every file is left unchanged
Employee.cs input
1 namespace Staff 2 { 3 public class Employee : Person 4 { 5 // The person this employee is. 6 private readonly Person _person = new Person { Name = "Ann" }; 7 8 public decimal Salary { get ; set ; } 9 10 public string Badge () => _person.Greeting ("Employee" ) + " (" + _person.Name + ")" ; 11 } 12 }
Person.cs input
1 namespace Staff 2 { 3 public class Person 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string LastName () => Name .Split (" " )[^1 ]; 8 9 public string Greeting (string salutation) => salutation + " " + Name ; 10 } 11 }
View fixture on GitHub
# nullableNullable annotations and flow are unchanged
success
Target symbol T:Staff.Employee
Employee.cs modified
1 1 #nullable enable 2 2 3 3 namespace Staff 4 4 { 5 5 public class Employee : Person 6 6 { 7 − private readonly Person _person = new Person (); 8 − 9 − public string Display () => _person.Nickname ?? "anonymous" ; 7 + public string Display () => Nickname ?? "anonymous" ; 10 8 } 11 9 }
Person.cs unchanged
1 1 #nullable enable 2 2 3 3 namespace Staff 4 4 { 5 5 public class Person 6 6 { 7 7 public string ? Nickname { get ; set ; } 8 8 } 9 9 }
View fixture on GitHub
# overrides-base-member-rejectedRefuses a class that overrides a member of the base class, which the base class would then call
refusal
Target symbol T:Shop.Report
Refuses overrides-base-member; every file is left unchanged
Report.cs input
1 namespace Shop 2 { 3 public class Document 4 { 5 public virtual string Title () => "Document" ; 6 7 public string Heading () => "# " + Title (); 8 } 9 10 public class Report : Document 11 { 12 private readonly Document _document = new Document (); 13 14 public override string Title () => "Report" ; 15 16 public string Print () => _document.Heading (); 17 } 18 }
View fixture on GitHub
# parameter-hides-memberWhere a parameter shares the member's name, the use says this
success
Target symbol T:Staff.Employee
Employee.cs modified
1 1 namespace Staff 2 2 { 3 3 public class Employee : Person 4 4 { 5 − private readonly Person _person = new Person (); 6 − 7 5 public string Retitle (string title) 8 6 { 9 − var old = _person.title; 10 − _person.title = title; 7 + var old = this .title; 8 + this .title = title; 11 9 return old; 12 10 } 13 11 } 14 12 }
Person.cs unchanged
1 1 namespace Staff 2 2 { 3 3 public class Person 4 4 { 5 5 public string title = "" ; 6 6 } 7 7 }
View fixture on GitHub