RefactorMCP catalog

Replace Base Uses with Field

For a class that derives from another and holds a new instance of it in a private field: the class's own uses of the members it inherits go through the field instead, so nothing uses the part of the instance its base class makes up. Replace Inheritance with Delegation uses it just before removing the base class.

Arguments

ArgumentRequiredMeaning
fieldyesThe private field holding an instance of the base class

The target is the class, by symbol: "target": { "symbol": "T:Shop.Stack" }.

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, and not used anywhere yet.
  • The class's constructors pass no arguments to the base class, so the field's object and the base class part 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 class uses no protected member of the base class, which the field cannot reach.
  • No code outside the class, subclasses included, uses an inherited member through an instance of the class, explicitly or implicitly (a foreach calling GetEnumerator), and no code converts an instance of the class to the base class or an interface the base class implements. Such code would stop seeing what the class adds. Add Delegating Member gives the class its own members for those uses first.

Transformation

  • Inside the class, every use of an inherited instance member through an implicit this, this or base goes through the field: Add(value) becomes _list.Add(value), this[Count - 1] becomes _list[_list.Count - 1], base.Count becomes _list.Count.
  • Where a local or parameter shares the field's name, the field is written this._list.

Preserved

  • The behaviour of every member: the field's object holds what the base class part held, since both started alike and only the class reached either.
  • The base class and every member the class declares.
  • Comments and layout.

Limitations

  • Inherited static members are left as they are.
  • Members of object the base class overrides, such as ToString, still run on the base class part, which no longer changes; code calling them on the class is not checked.

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
field-in-usethe field is already used
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
uses-protected-memberthe class uses a protected member of the base class
base-members-in-usecode outside the class uses an inherited member through an instance of it
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-and-this-qualified

Uses written with base or this go through the field, including those in members that forward to the base class, while code elsewhere keeps using the class's own members; comments stay

success
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Stack.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
55 // Last in, first out.
66 public class Stack : List<int>
77 {
88 private readonly List<int> _list = new List<int>();
99
10− public new int Count => base.Count;
10+ public new int Count => _list.Count;
1111
12− public new int this[int index] { get => base[index]; set => base[index] = value; }
12+ public new int this[int index] { get => _list[index]; set => _list[index] = value; }
1313
14− public void Push(int value) => this.Add(value); // on top
14+ public void Push(int value) => _list.Add(value); // on top
1515
1616 public int Pop()
1717 {
1818 // The last element is the top.
19− var top = base[base.Count - 1];
20− base.RemoveAt(base.Count - 1);
19+ var top = _list[_list.Count - 1];
20+ _list.RemoveAt(_list.Count - 1);
2121 return top;
2222 }
2323 }
2424 }
Client.csunchanged
11 namespace Shop
22 {
33 public class Client
44 {
55 public int Run()
66 {
77 var stack = new Stack();
88 stack.Push(1);
99 stack[0] = 5;
1010 return stack.Pop() + stack.Count;
1111 }
1212 }
1313 }

#base-constructed-with-arguments-rejected

Refuses when a constructor passes arguments to the base class, so the field would not start the same

refusal
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Refusesbase-constructed-with-arguments; every file is left unchanged
Stack.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Stack : List<int>
6 {
7 private readonly List<int> _list = new List<int>();
8
9 public Stack(IEnumerable<int> values) : base(values)
10 {
11 }
12
13 public void Push(int value) => Add(value);
14 }
15 }

#base-conversion-in-use-rejected

Refuses when code converts the class to its base class or one of its interfaces, which reaches the base class part

refusal
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Refusesbase-conversion-in-use mentioning “Client.cs(11,”; every file is left unchanged
Client.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Client
6 {
7 public int Run()
8 {
9 var stack = new Stack();
10 stack.Push(1);
11 IEnumerable<int> values = stack;
12 return stack.Pop();
13 }
14 }
15 }
Stack.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Stack : List<int>
6 {
7 private readonly List<int> _list = new List<int>();
8
9 public void Push(int value) => Add(value);
10
11 public int Pop()
12 {
13 var top = this[Count - 1];
14 RemoveAt(Count - 1);
15 return top;
16 }
17 }
18 }

#base-members-in-use-rejected

Refuses when code elsewhere uses a member the class inherits, which would no longer see what the class adds

refusal
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Refusesbase-members-in-use mentioning “Client.cs(9,”; every file is left unchanged
Client.csinput
1 namespace Shop
2 {
3 public class Client
4 {
5 public int Run()
6 {
7 var stack = new Stack();
8 stack.Push(1);
9 return stack.Count;
10 }
11 }
12 }
Stack.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Stack : List<int>
6 {
7 private readonly List<int> _list = new List<int>();
8
9 public void Push(int value) => Add(value);
10
11 public int Pop()
12 {
13 var top = this[Count - 1];
14 RemoveAt(Count - 1);
15 return top;
16 }
17 }
18 }

#field-in-use-rejected

Refuses a field the class already uses, whose contents may differ from the base class part

refusal
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Refusesfield-in-use; every file is left unchanged
Stack.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Stack : List<int>
6 {
7 private readonly List<int> _list = new List<int>();
8
9 public void Push(int value) => Add(value);
10
11 public void Log(int value) => _list.Add(value);
12 }
13 }

#field-not-found-rejected

Refuses when the class has no field of that name

refusal
Targetsymbol T:Shop.Stack
Arguments
field"_items"
Refusesfield-not-found mentioning “_items”; every file is left unchanged
Stack.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Stack : List<int>
6 {
7 private readonly List<int> _list = new List<int>();
8
9 public void Push(int value) => Add(value);
10
11 public int Pop()
12 {
13 var top = this[Count - 1];
14 RemoveAt(Count - 1);
15 return top;
16 }
17 }
18 }

#field-not-private-rejected

Refuses a field other code could reach, which may hold something else by the time the class uses it

refusal
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Refusesfield-not-private; every file is left unchanged
Stack.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Stack : List<int>
6 {
7 internal readonly List<int> _list = new List<int>();
8
9 public void Push(int value) => Add(value);
10
11 public int Pop()
12 {
13 var top = this[Count - 1];
14 RemoveAt(Count - 1);
15 return top;
16 }
17 }
18 }

#field-type-not-base-rejected

Refuses a field whose type is not the base class, type arguments included

refusal
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Refusesfield-type-not-base; every file is left unchanged
Stack.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Stack : List<int>
6 {
7 private readonly List<long> _list = new List<long>();
8
9 public void Push(int value) => Add(value);
10
11 public int Pop()
12 {
13 var top = this[Count - 1];
14 RemoveAt(Count - 1);
15 return top;
16 }
17 }
18 }

#generic-base

A generic class deriving from a constructed generic base uses a field of the same constructed type

success
Targetsymbol T:Shop.Registry`1
Arguments
field"_items"
Registry.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
55 public class Registry<T> : List<T>
66 {
77 private readonly List<T> _items = new List<T>();
88
99 public void Register(T item)
1010 {
11− if (!Contains(item))
12− Add(item);
11+ if (!_items.Contains(item))
12+ _items.Add(item);
1313 }
1414 }
1515 }

#member-of-grandbase

Members the base class itself inherits are reached through the field too

success
Targetsymbol T:Staff.Manager
Arguments
field"_employee"
People.csmodified
11 namespace Staff
22 {
33 public class Person
44 {
55 public string Name { get; set; } = "";
66 }
77
88 public class Employee : Person
99 {
1010 public decimal Salary { get; set; }
1111 }
1212
1313 public class Manager : Employee
1414 {
1515 private readonly Employee _employee = new Employee();
1616
17− public string Title() => "Manager " + Name + " on " + Salary;
17+ public string Title() => "Manager " + _employee.Name + " on " + _employee.Salary;
1818 }
1919 }

#minimal

The class's uses of the members it inherits go through the field instead

success
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Stack.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
55 public class Stack : List<int>
66 {
77 private readonly List<int> _list = new List<int>();
88
9− public void Push(int value) => Add(value);
9+ public void Push(int value) => _list.Add(value);
1010
1111 public int Pop()
1212 {
13− var top = this[Count - 1];
14− RemoveAt(Count - 1);
13+ var top = _list[_list.Count - 1];
14+ _list.RemoveAt(_list.Count - 1);
1515 return top;
1616 }
1717 }
1818 }
Client.csunchanged
11 namespace Shop
22 {
33 public class Client
44 {
55 public int Run()
66 {
77 var stack = new Stack();
88 stack.Push(1);
99 return stack.Pop();
1010 }
1111 }
1212 }

#no-base-class-rejected

Refuses a class that derives only from object

refusal
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Refusesno-base-class; every file is left unchanged
Stack.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Stack
6 {
7 private readonly List<int> _list = new List<int>();
8
9 public void Push(int value) => _list.Add(value);
10 }
11 }

#not-a-class-rejected

Refuses a struct, which has no base class to stop using

refusal
Targetsymbol T:Shop.Point
Arguments
field"_x"
Refusesnot-a-class; every file is left unchanged
Point.csinput
1 namespace Shop
2 {
3 public struct Point
4 {
5 private readonly int _x;
6
7 public Point(int x) => _x = x;
8
9 public int X => _x;
10 }
11 }

#not-created-by-field-rejected

Refuses a field not initialised the way the base class part was constructed: here with a capacity

refusal
Targetsymbol T:Shop.Stack
Arguments
field"_list"
Refusesnot-created-by-field; every file is left unchanged
Stack.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Stack : List<int>
6 {
7 private readonly List<int> _list = new List<int>(16);
8
9 public void Push(int value) => Add(value);
10
11 public int Pop()
12 {
13 var top = this[Count - 1];
14 RemoveAt(Count - 1);
15 return top;
16 }
17 }
18 }

#nullable

Nullable annotations are untouched when the uses move to the field

success
Targetsymbol T:Shop.Cache
Arguments
field"_entries"
Cache.csmodified
11 #nullable enable
22
33 using System.Collections.Generic;
44
55 namespace Shop
66 {
77 public class Cache : Dictionary<string, string?>
88 {
99 private readonly Dictionary<string, string?> _entries = new Dictionary<string, string?>();
1010
11− public string? Lookup(string key) => TryGetValue(key, out var value) ? value : null;
11+ public string? Lookup(string key) => _entries.TryGetValue(key, out var value) ? value : null;
1212 }
1313 }

#overrides-base-member-rejected

Refuses a class that overrides a member of the base class, which the field's object would not 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() => Heading();
17 }
18 }

#uses-protected-member-rejected

Refuses a class that uses a protected member of the base class, which the field cannot reach

refusal
Targetsymbol T:Shop.Report
Arguments
field"_document"
Refusesuses-protected-member mentioning “Stamp”; every file is left unchanged
Report.csinput
1 namespace Shop
2 {
3 public class Document
4 {
5 public string Body { get; set; } = "";
6
7 protected string Stamp() => "[draft]";
8 }
9
10 public class Report : Document
11 {
12 private readonly Document _document = new Document();
13
14 public string Print() => Stamp() + Body;
15 }
16 }