RefactorMCP catalog

Replace Field Uses with Base

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

ArgumentRequiredMeaning
fieldyesThe 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.

Transformation

  • 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

CodeMeaning
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

·

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-rejected

Refuses when a constructor passes arguments to the base class, so the instance would not start like the field's object

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Refusesbase-constructed-with-arguments; every file is left unchanged
Employee.csinput
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.csinput
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 }

#base-conversion-in-use-rejected

Refuses when code converts the class to its base class, which would then see what the field held

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Refusesbase-conversion-in-use mentioning “Payroll.cs(5,”; every file is left unchanged
Employee.csinput
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.csinput
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.csinput
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 }

#base-members-in-use-rejected

Refuses when code already uses an inherited member, which would then see what the field held

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Refusesbase-members-in-use mentioning “Payroll.cs(5,”; every file is left unchanged
Employee.csinput
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.csinput
1 namespace Staff
2 {
3 public class Payroll
4 {
5 public string Slip(Employee employee) => employee.LastName() + ": " + employee.Badge();
6 }
7 }
Person.csinput
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 }

#field-assigned-rejected

Refuses a field assigned after its initialiser, which may then hold another object

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Refusesfield-assigned mentioning “Employee.cs(11,”; every file is left unchanged
Employee.csinput
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.csinput
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 }

#field-escapes-rejected

Refuses a field used as an object in its own right, which the instance could not stand in for

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Refusesfield-escapes mentioning “Employee.cs(9,”; every file is left unchanged
Employee.csinput
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.csinput
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 }

#field-in-region

A region around the field stays, and the field's own comment goes with it

success
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Employee.csmodified
11 namespace Staff
22 {
33 public class Employee : Person
44 {
55 #region Delegate
6− // The person this employee is.
7− private readonly Person _person = new Person();
86 #endregion
97
10− public string Badge() => _person.Greeting("Employee");
8+ public string Badge() => Greeting("Employee");
119 }
1210 }
Person.csunchanged
11 namespace Staff
22 {
33 public class Person
44 {
55 public string Name { get; set; } = "";
66
77 public string LastName() => Name.Split(" ")[^1];
88
99 public string Greeting(string salutation) => salutation + " " + Name;
1010 }
1111 }

#field-not-found-rejected

Refuses when the class has no field of that name

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_self"
Refusesfield-not-found mentioning “_self”; every file is left unchanged
Employee.csinput
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.csinput
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 }

#field-not-private-rejected

Refuses a field other code could reach

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Refusesfield-not-private; every file is left unchanged
Employee.csinput
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.csinput
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 }

#field-type-not-base-rejected

Refuses a field whose type is not the base class

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Refusesfield-type-not-base; every file is left unchanged
Contact.csinput
1 namespace Staff
2 {
3 public class Contact
4 {
5 public string Email { get; set; } = "";
6 }
7 }
Employee.csinput
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.csinput
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 }

#generic-base

With a constructed generic base, member and element accesses through the field become accesses on the instance

success
Targetsymbol T:Shop.Inventory
Arguments
field"_items"
Inventory.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
55 public class Inventory : List<string>
66 {
7− private readonly List<string> _items = new List<string>();
8−
97 public string Receive(string item)
108 {
11− _items.Add(item);
12− return _items[0] + " of " + _items.Count;
9+ Add(item);
10+ return this[0] + " of " + Count;
1311 }
1412 }
1513 }

#hidden-members-use-base

Where a member of the class hides the inherited one, including inside that member, the use says base; callers elsewhere are untouched

success
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Employee.csmodified
11 namespace Staff
22 {
33 public class Employee : Person
44 {
5− // The person this employee is.
6− private readonly Person _person = new Person();
7−
85 public decimal Salary { get; set; }
96
107 public string Name
118 {
12− get => _person.Name;
13− set => _person.Name = value;
9+ get => base.Name;
10+ set => base.Name = value;
1411 }
1512
1613 public string LastName()
1714 {
18− return _person.LastName();
15+ return base.LastName();
1916 }
2017
21− public string Badge() => _person.Greeting("Employee") + " (" + this._person.LastName() + ")";
18+ public string Badge() => Greeting("Employee") + " (" + base.LastName() + ")";
2219 }
2320 }
Payroll.csunchanged
11 namespace Staff
22 {
33 public class Payroll
44 {
55 public string Slip(Employee employee)
66 {
77 employee.Name = "Ann Lee";
88 return employee.LastName() + ": " + employee.Salary + " " + employee.Badge();
99 }
1010 }
1111 }
Person.csunchanged
11 namespace Staff
22 {
33 public class Person
44 {
55 public string Name { get; set; } = "";
66
77 public string LastName() => Name.Split(" ")[^1];
88
99 public string Greeting(string salutation) => salutation + " " + Name;
1010 }
1111 }

#minimal

Uses of the field's members become uses of the members the class inherits, and the field goes with its comment

success
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Employee.csmodified
11 namespace Staff
22 {
33 public class Employee : Person
44 {
5− // The person this employee is.
6− private readonly Person _person = new Person();
7−
85 public decimal Salary { get; set; }
96
10− public string Badge() => _person.Greeting("Employee") + " (" + _person.Name + ")";
7+ public string Badge() => Greeting("Employee") + " (" + Name + ")";
118 }
129 }
Payroll.csunchanged
11 namespace Staff
22 {
33 public class Payroll
44 {
55 public string Slip(Employee employee) => employee.Badge() + ": " + employee.Salary;
66 }
77 }
Person.csunchanged
11 namespace Staff
22 {
33 public class Person
44 {
55 public string Name { get; set; } = "";
66
77 public string LastName() => Name.Split(" ")[^1];
88
99 public string Greeting(string salutation) => salutation + " " + Name;
1010 }
1111 }

#no-base-class-rejected

Refuses a class that derives only from object

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Refusesno-base-class; every file is left unchanged
Employee.csinput
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.csinput
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 }

#not-a-class-rejected

Refuses a struct, which has no base class

refusal
Targetsymbol T:Staff.Badge
Arguments
field"_number"
Refusesnot-a-class; every file is left unchanged
Badge.csinput
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 }

#not-created-by-field-rejected

Refuses a field whose object is set up differently from a new instance, here by an object initializer

refusal
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Refusesnot-created-by-field; every file is left unchanged
Employee.csinput
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.csinput
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 }

#nullable

Nullable annotations and flow are unchanged

success
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Employee.csmodified
11 #nullable enable
22
33 namespace Staff
44 {
55 public class Employee : Person
66 {
7− private readonly Person _person = new Person();
8−
9− public string Display() => _person.Nickname ?? "anonymous";
7+ public string Display() => Nickname ?? "anonymous";
108 }
119 }
Person.csunchanged
11 #nullable enable
22
33 namespace Staff
44 {
55 public class Person
66 {
77 public string? Nickname { get; set; }
88 }
99 }

#overrides-base-member-rejected

Refuses a class that overrides a member of the base class, which the base class would then call

refusal
Targetsymbol T:Shop.Report
Arguments
field"_document"
Refusesoverrides-base-member; every file is left unchanged
Report.csinput
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 }

#parameter-hides-member

Where a parameter shares the member's name, the use says this

success
Targetsymbol T:Staff.Employee
Arguments
field"_person"
Employee.csmodified
11 namespace Staff
22 {
33 public class Employee : Person
44 {
5− private readonly Person _person = new Person();
6−
75 public string Retitle(string title)
86 {
9− var old = _person.title;
10− _person.title = title;
7+ var old = this.title;
8+ this.title = title;
119 return old;
1210 }
1311 }
1412 }
Person.csunchanged
11 namespace Staff
22 {
33 public class Person
44 {
55 public string title = "";
66 }
77 }