RefactorMCP catalog

Change Base Type

Sets, replaces or removes the base class of a class. Extract Superclass and Replace Inheritance with Delegation use it to move a class under a new parent or off an old one.

Arguments

ArgumentRequiredMeaning
tonoThe new base class, as it would be written in the class's file. Omit it to remove the base class

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

Precondition

  • The target is a class; structs, interfaces and enums have no base class.
  • to names a class that is neither sealed nor static, and that does not already derive from the target.
  • Removing needs a base class to remove.
  • Nothing relies on the old base in a way the new one does not support: the class's own members, its constructors' base(...) calls, its overrides, and code elsewhere that uses inherited members or converts the class to the old base must all still compile.

Transformation

  • An existing base class is replaced in place; otherwise the new base class goes first in the base list, ahead of any interfaces.
  • Removing the base class keeps the interfaces, and drops the colon when the base class was all the list held.
  • When the new base class only binds with a using directive, the directive is added, and a qualified name the usings already cover is shortened.
  • A new modifier on a member of the class that no longer hides anything, such as a forwarding member once the base class it hid is removed, is dropped.

Preserved

  • The interfaces the class implements and their order.
  • Comments and line breaks around the declaration.
  • Every constructor chain and override, which is why a change that would break one is refused rather than repaired.

Limitations

  • Nothing is adapted to the new base: members the class used from the old base are not recreated, and base(...) calls are not rewritten. Composites that need that pull members up or introduce delegation first.
  • Records are not covered.

Error codes

CodeMeaning
not-a-classthe target is a struct, interface or other non-class type
no-base-classthere is no base class to remove
type-not-foundto names no type the class's file can see
invalid-base-typeto is sealed, static, an interface or not a class
circular-baseto already derives from the target
base-members-in-usethe class or code using it needs a member, constructor or overridden method only the old base has
base-conversion-in-usecode converts the class to the old base

Cases

·

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

#add-base-class

Gives a class with no base class one

success
Targetsymbol T:Shop.Manager
Arguments
to"Shop.Employee"
Manager.csmodified
11 namespace Shop
22 {
3− public class Manager
3+ public class Manager : Employee
44 {
55 public int Grade;
66 }
77 }
Employee.csunchanged
11 namespace Shop
22 {
33 public class Employee
44 {
55 public string Name;
66 }
77 }

#add-before-interfaces

Puts the new base class before the interfaces the class already implements

success
Targetsymbol T:Shop.Manager
Arguments
to"Employee"
Manager.csmodified
11 using System;
22
33 namespace Shop
44 {
5− public class Manager : IDisposable
5+ public class Manager : Employee, IDisposable
66 {
77 public void Dispose()
88 {
99 }
1010 }
1111 }
Employee.csunchanged
11 namespace Shop
22 {
33 public class Employee
44 {
55 }
66 }

#callers-use-old-base

Refuses when code in another file relies on the class converting to the old base class

refusal
Targetsymbol T:Shop.Manager
Arguments
to"Vendor"
Refusesbase-conversion-in-use mentioning “Payroll.cs”; every file is left unchanged
Payroll.csinput
1 namespace Shop
2 {
3 public static class Payroll
4 {
5 public static void Pay(Person person)
6 {
7 }
8
9 public static void PayManager(Manager manager) => Pay(manager);
10 }
11 }
People.csinput
1 namespace Shop
2 {
3 public class Person
4 {
5 }
6
7 public class Vendor
8 {
9 }
10
11 public class Manager : Person
12 {
13 }
14 }

#circular-base

Refuses a base class that already derives from the class

refusal
Targetsymbol T:Shop.Employee
Arguments
to"Manager"
Refusescircular-base; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Employee
4 {
5 }
6
7 public class Manager : Employee
8 {
9 }
10 }

#constructor-chains-to-new-base

Keeps a base constructor call the new base class also accepts

success
Targetsymbol T:Shop.Manager
Arguments
to"Employee"
People.csmodified
11 namespace Shop
22 {
33 public class Person
44 {
55 public Person(string name)
66 {
77 Name = name;
88 }
99
1010 public string Name { get; }
1111 }
1212
1313 public class Employee : Person
1414 {
1515 public Employee(string name)
1616 : base(name)
1717 {
1818 }
1919 }
2020
21− public class Manager : Person
21+ public class Manager : Employee
2222 {
2323 public Manager(string name)
2424 : base(name)
2525 {
2626 }
2727 }
2828 }

#generic-base-in-another-namespace

Derives from a constructed generic class in another namespace and imports it

success
Targetsymbol T:Shop.OrderStore
Arguments
to"Repository<Order>"
OrderStore.csmodified
1+using Shop.Data;
2+
13 namespace Shop
24 {
35 public class Order
46 {
57 }
68
7− public class OrderStore
9+ public class OrderStore : Repository<Order>
810 {
911 }
1012 }
Data/Repository.csunchanged
11 using System.Collections.Generic;
22
33 namespace Shop.Data
44 {
55 public abstract class Repository<T>
66 {
77 protected readonly List<T> Items = new List<T>();
88 }
99 }

#interface-as-base

Refuses an interface, which a class implements rather than derives from

refusal
Targetsymbol T:Shop.Manager
Arguments
to"System.IDisposable"
Refusesinvalid-base-type mentioning “interface”; every file is left unchanged
Manager.csinput
1 namespace Shop
2 {
3 public class Manager
4 {
5 }
6 }

#members-use-old-base

Refuses when the class uses a member the new base class does not have

refusal
Targetsymbol T:Shop.Manager
Arguments
to"Vendor"
Refusesbase-members-in-use mentioning “Name”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Person
4 {
5 public string Name;
6 }
7
8 public class Vendor
9 {
10 public string Company;
11 }
12
13 public class Manager : Person
14 {
15 public string Describe() => "Manager " + Name;
16 }
17 }

#new-modifiers-no-longer-needed

Removing the base class drops the new modifier from members that hid its members and now hide nothing

success
Targetsymbol T:Shop.Stack
Stack.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
5− public class Stack : List<int>
5+ public class Stack
66 {
77 private readonly List<int> _list = new List<int>();
88
9− public new int Count => _list.Count;
9+ public int Count => _list.Count;
1010
11− public new int this[int index] { get => _list[index]; set => _list[index] = value; }
11+ public int this[int index] { get => _list[index]; set => _list[index] = value; }
1212
1313 public void Push(int value) => _list.Add(value);
1414 }
1515 }

#no-base-to-remove

Refuses to remove a base class the class does not have

refusal
Targetsymbol T:Shop.Manager
Refusesno-base-class; every file is left unchanged
Manager.csinput
1 namespace Shop
2 {
3 public class Manager
4 {
5 }
6 }

#not-a-class

Refuses a struct, which cannot have a base class

refusal
Targetsymbol T:Shop.Money
Arguments
to"Amount"
Refusesnot-a-class; every file is left unchanged
Money.csinput
1 namespace Shop
2 {
3 public class Amount
4 {
5 }
6
7 public struct Money
8 {
9 }
10 }

#override-of-old-base

Refuses when the class overrides a member only the old base class declares

refusal
Targetsymbol T:Shop.Manager
Arguments
to"Vendor"
Refusesbase-members-in-use mentioning “Describe”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public class Person
4 {
5 public virtual string Describe() => "Person";
6 }
7
8 public class Vendor
9 {
10 }
11
12 public class Manager : Person
13 {
14 public override string Describe() => "Manager";
15 }
16 }

#remove-base-class

Removes the base class when no target is given, keeping the interfaces

success
Targetsymbol T:Shop.Manager
Manager.csmodified
11 using System;
22
33 namespace Shop
44 {
55 public class Employee
66 {
77 }
88
9− public class Manager : Employee, IDisposable
9+ public class Manager : IDisposable
1010 {
1111 public void Dispose()
1212 {
1313 }
1414 }
1515 }

#remove-only-base-class

Removes the whole base list when the base class was all it held

success
Targetsymbol T:Shop.Manager
Manager.csmodified
11 namespace Shop
22 {
33 public class Employee
44 {
55 }
66
7− public class Manager : Employee
7+ public class Manager
88 {
99 public int Grade;
1010 }
1111 }

#replace-keeps-interfaces-and-comments

Replaces the base class with a subclass of it, keeping the interfaces and the comments around the declaration

success
Targetsymbol T:Shop.Manager
Arguments
to"Employee"
Manager.csmodified
11 using System;
22
33 namespace Shop
44 {
55 // Managers approve orders.
6− public class Manager : Person, IComparable<Manager> // ordered by name
6+ public class Manager : Employee, IComparable<Manager> // ordered by name
77 {
88 public int CompareTo(Manager other) => string.CompareOrdinal(Name, other.Name);
99 }
1010 }
People.csunchanged
11 using System;
22
33 namespace Shop
44 {
55 public class Person
66 {
77 public string Name;
88 }
99
1010 public class Employee : Person
1111 {
1212 public decimal Salary;
1313 }
1414 }

#sealed-base

Refuses a sealed base class

refusal
Targetsymbol T:Shop.Manager
Arguments
to"Director"
Refusesinvalid-base-type mentioning “sealed”; every file is left unchanged
People.csinput
1 namespace Shop
2 {
3 public sealed class Director
4 {
5 }
6
7 public class Manager
8 {
9 }
10 }

#type-not-found

Refuses a base class that names no type

refusal
Targetsymbol T:Shop.Manager
Arguments
to"Supervisor"
Refusestype-not-found mentioning “Supervisor”; every file is left unchanged
Manager.csinput
1 namespace Shop
2 {
3 public class Manager
4 {
5 }
6 }