RefactorMCP catalog

Remove Delegating Member

Removes a member that only forwards, through base, to the inherited member it hides or overrides, so its callers reach the inherited member directly. Replace Delegation with Inheritance uses it once a class's forwarding members reach their targets through base.

Arguments

None. The target is the member, by symbol: "target": { "symbol": "M:Staff.Employee.LastName" }, or for an indexer "target": { "symbol": "P:Shop.Stack.Item(System.Int32)" }.

Precondition

  • The member is a method, property or indexer whose whole body reaches the inherited member of the same name through base: a method whose body is base.M(a, b), passing its own parameters in order and by the same ref kind; a property whose getter reads base.P and whose setter, if any, assigns base.P = value; an indexer that does the same with base[i]. Expression and block bodies are both recognised.
  • The inherited member has the same signature: the same return or property type, and parameters with the same names, types, modifiers and default values, so no call binds or behaves differently.
  • The inherited member, and its setter where the member has one, is at least as accessible as the member.
  • The member is not a new virtual member that subclasses override.
  • The result compiles.

Transformation

  • The member is removed with its comments and documentation.
  • Inside the class, uses of the inherited member written with base become plain uses (base.LastName() becomes LastName(), base[i] becomes this[i]), unless a subclass overrides it, where base is needed to keep reaching the inherited implementation.

Preserved

  • Every call of the removed member, which now reaches the inherited member it forwarded to.
  • Comments and blank lines around the other members.

Limitations

  • Generic methods and members with attributes or modifiers on their accessors are not recognised as forwarding.
  • A member forwarding to a field rather than base is not removed; Replace Field Uses with Base turns such a field into the instance first.

Error codes

CodeMeaning
not-delegatingthe member does more than reach the inherited member of the same name through base
signature-differsthe inherited member's signature differs, for example in a parameter's name or default value
inherited-less-accessiblethe inherited member or its setter is less accessible than the member
member-overriddenthe member is virtual and a subclass overrides it

Cases

·

Diffs show each file from before to after. /*[*/ … /*]*/ marks a selection and /*^*/ a caret; the runner removes them before the refactoring runs.

#base-use-kept-for-overridden-member

A use written with base stays when a subclass overrides the member, since without base it would reach the override

success
Targetsymbol M:Shop.Report.Title
Report.csmodified
11 namespace Shop
22 {
33 public class Document
44 {
55 public virtual string Title() => "Document";
66 }
77
88 public class Report : Document
99 {
10− public override string Title() => base.Title();
11−
1210 public string Print() => "# " + base.Title();
1311 }
1412
1513 public class AnnualReport : Report
1614 {
1715 public override string Title() => "Annual report";
1816 }
1917 }

#base-uses-simplified

Uses of the inherited member written with base, needed only while the removed member hid it, become plain uses

success
Targetsymbol M:Staff.Employee.LastName
Employee.csmodified
11 namespace Staff
22 {
33 public class Employee : Person
44 {
5− public string LastName()
6− {
7− return base.LastName();
8− }
9−
10− public string Badge() => Greeting("Employee") + " (" + base.LastName() + ")";
5+ public string Badge() => Greeting("Employee") + " (" + LastName() + ")";
116 }
127 }
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.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 }

#comments-go-with-member

The member's documentation comment goes with it; the neighbours keep theirs and the blank lines between them

success
Targetsymbol M:Staff.Employee.LastName
Employee.csmodified
11 namespace Staff
22 {
33 public class Employee : Person
44 {
55 // What the employee is paid.
66 public decimal Salary { get; set; }
77
8− /// <summary>The employee's last name.</summary>
9− public new string LastName() => base.LastName();
10−
118 // A label for the badge.
129 public string Badge() => "Employee " + Name;
1310 }
1411 }
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 }

#forwards-to-another-member-rejected

Refuses a member that forwards to an inherited member of another name, which callers would not reach once it is gone

refusal
Targetsymbol M:Staff.Employee.Surname
Refusesnot-delegating; every file is left unchanged
Employee.csinput
1 namespace Staff
2 {
3 public class Employee : Person
4 {
5 public string Surname() => base.LastName();
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 }

#generic

A forwarding member of a generic class, in terms of its type parameter, is removed

success
Targetsymbol M:Shop.Registry`1.Add(`0)
Registry.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
55 public class Registry<T> : List<T>
66 {
7− public new void Add(T item) => base.Add(item);
8−
97 public void Register(T item)
108 {
119 if (!Contains(item))
1210 Add(item);
1311 }
1412 }
1513 }

#indexer

Removes an indexer that only reaches the inherited indexer; element accesses written with base become accesses on this

success
Targetsymbol P:Shop.Stack.Item(System.Int32)
Stack.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
55 public class Stack : List<int>
66 {
7− public new int this[int index] { get => base[index]; set => base[index] = value; }
8−
9− public int Peek() => base[Count - 1];
7+ public int Peek() => this[Count - 1];
108 }
119 }

#inherited-less-accessible-rejected

Refuses when the inherited member is less accessible than the one forwarding to it

refusal
Targetsymbol M:Staff.Employee.Code
Refusesinherited-less-accessible; every file is left unchanged
Employee.csinput
1 namespace Staff
2 {
3 public class Employee : Person
4 {
5 public new string Code() => base.Code();
6 }
7 }
Person.csinput
1 namespace Staff
2 {
3 public class Person
4 {
5 protected string Code() => "P-1";
6 }
7 }

#member-overridden-rejected

Refuses a virtual member a subclass overrides, which would be left overriding something else

refusal
Targetsymbol M:Staff.Employee.LastName
Refusesmember-overridden mentioning “Manager”; every file is left unchanged
Employee.csinput
1 namespace Staff
2 {
3 public class Employee : Person
4 {
5 public new virtual string LastName() => base.LastName();
6 }
7
8 public class Manager : Employee
9 {
10 public override string LastName() => "Manager " + base.LastName();
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 }

#method

Removes a method that only calls the method it hides through base; callers now reach the inherited method

success
Targetsymbol M:Staff.Employee.LastName
Employee.csmodified
11 namespace Staff
22 {
33 public class Employee : Person
44 {
55 public decimal Salary { get; set; }
66
7− public new string LastName()
8− {
9− return base.LastName();
10− }
11−
127 public string Badge() => "Employee " + Name;
138 }
149 }
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.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 }

#not-delegating-rejected

Refuses a member that does more than reach the inherited member

refusal
Targetsymbol M:Staff.Employee.LastName
Refusesnot-delegating; every file is left unchanged
Employee.csinput
1 namespace Staff
2 {
3 public class Employee : Person
4 {
5 public new string LastName() => base.LastName().ToUpperInvariant();
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 }

#nullable

A forwarding member with nullable types is removed like any other

success
Targetsymbol P:Staff.Employee.Nickname
Employee.csmodified
11 #nullable enable
22
33 namespace Staff
44 {
55 public class Employee : Person
66 {
7− public new string? Nickname => base.Nickname;
8−
97 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 }

#overload-by-id

Removes the one overload the id names

success
Targetsymbol M:Shop.Report.Print(System.String)
Report.csmodified
11 namespace Shop
22 {
33 public class Report : Printer
44 {
5− public new void Print(string text) => base.Print(text);
6−
75 public new void Print(int number) => base.Print(number);
86 }
97 }
Printer.csunchanged
11 namespace Shop
22 {
33 public class Printer
44 {
55 public void Print(string text) { }
66
77 public void Print(int number) { }
88 }
99 }

#override

Removes an override that only calls the member it overrides

success
Targetsymbol M:Shop.Report.Title
Report.csmodified
11 namespace Shop
22 {
33 public class Document
44 {
55 public virtual string Title() => "Document";
66 }
77
88 public class Report : Document
99 {
10− public override string Title() => base.Title();
11−
1210 public string Print() => "# " + Title();
1311 }
1412 }

#property-with-setter

Removes a property whose getter and setter only reach the inherited property through base

success
Targetsymbol P:Staff.Employee.Name
Employee.csmodified
11 namespace Staff
22 {
33 public class Employee : Person
44 {
55 public decimal Salary { get; set; }
66
7− public new string Name
8− {
9− get => base.Name;
10− set => base.Name = value;
11− }
12−
137 public string Badge() => "Employee " + Name;
148 }
159 }
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.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 }

#signature-differs-rejected

Refuses a member whose parameters differ from the inherited member's, here in a default value callers rely on

refusal
Targetsymbol M:Staff.Employee.Greeting(System.String)
Refusessignature-differs; every file is left unchanged
Employee.csinput
1 namespace Staff
2 {
3 public class Employee : Person
4 {
5 public new string Greeting(string salutation = "Hello") => base.Greeting(salutation);
6
7 public string Badge() => Greeting();
8 }
9 }
Person.csinput
1 namespace Staff
2 {
3 public class Person
4 {
5 public string Name { get; set; } = "";
6
7 public string Greeting(string salutation = "Dear") => salutation + " " + Name;
8 }
9 }