RefactorMCP catalog

Extract Interface

Declares some or all of a class's public members in a new interface, in a new file, and makes the class implement it. Introduce Interface for Dependency uses it before changing the type of a field or parameter to the interface.

Arguments

ArgumentRequiredMeaning
nameyesThe interface's name, such as IOrder
membersnoThe member names to declare, as an array. A method name takes every overload; this names the indexer. Omit it for every public instance member
filenoThe file for the interface, relative to the solution. Defaults to <name>.cs beside the class

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

Precondition

  • Every named member exists and is a public instance method, property, indexer or event, so the class can implement it implicitly.
  • Without members, the class has at least one such member.
  • No type with the interface's name and the class's arity exists in the class's namespace.
  • The result compiles.

Transformation

  • The interface is public, in the class's namespace, following the namespace style of the class's file, and has the class's type parameters and constraints: Repository<T> where T : class gives IRepository<T> where T : class.
  • Members appear in the class's order, one per line, each with its documentation comment. Methods keep their parameters, defaults and constraints; properties and indexers declare only the accessors callers can use, so { get; private set; } becomes { get; } and an expression body becomes { get; }.
  • The interface file has the usings its signatures need and no others.
  • The interface is added after the class's existing base class and interfaces.

Preserved

  • The class's members and layout; only its base list changes.
  • Every caller, which still uses the class.

Limitations

  • Existing parameters and fields of the class's type are not changed to the interface; Change Type does that.
  • Attributes on members are not copied.
  • An event field declaring several events at once cannot be chosen.

Error codes

CodeMeaning
member-not-founda named member does not exist in the class
member-not-eligiblea named member is not a public instance member
no-membersno member names were given and the class has no public instance members
type-already-existsthe namespace already has a type with the interface's name
breaks-compilationthe result would not compile

Cases

·

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

#all-public-members

Takes every public instance member when none are named, including events and indexers, and imports what their signatures need

success
Targetsymbol T:Shop.Customer
Arguments
name"ICustomer"
Customer.csmodified
11 using System;
22 using System.Linq;
33
44 namespace Shop
55 {
6− public class Customer
6+ public class Customer : ICustomer
77 {
88 private readonly string[] _tags = new string[3];
99
1010 public string Name { get; set; }
1111
1212 public event EventHandler Renamed;
1313
1414 public string this[int index] => _tags[index];
1515
1616 public void Rename(string name)
1717 {
1818 Name = name;
1919 Renamed?.Invoke(this, EventArgs.Empty);
2020 }
2121
2222 public int TagCount() => _tags.Count(t => t != null);
2323
2424 public static Customer Create() => new Customer();
2525
2626 private void Reset() => Name = null;
2727 }
2828 }
ICustomer.csadded
1+using System;
2+
3+namespace Shop
4+{
5+ public interface ICustomer
6+ {
7+ string Name { get; set; }
8+ event EventHandler Renamed;
9+ string this[int index] { get; }
10+ void Rename(string name);
11+ int TagCount();
12+ }
13+}

#doc-comments-in-another-folder

Writes the interface to the given file, keeping the members' documentation comments and only the usings the signatures need

success
Targetsymbol T:Shop.Billing
Arguments
name"IBilling"
file"Contracts/IBilling.cs"
Billing.csmodified
11 using System.Collections.Generic;
22 using System.Linq;
33 using System.Threading.Tasks;
44
55 namespace Shop
66 {
7− public class Billing
7+ public class Billing : IBilling
88 {
99 private readonly List<decimal> _amounts = new List<decimal>();
1010
1111 /// <summary>
1212 /// The amounts billed so far.
1313 /// </summary>
1414 public IReadOnlyList<decimal> Amounts => _amounts;
1515
1616 /// <summary>Totals every amount.</summary>
1717 public Task<decimal> TotalAsync() => Task.FromResult(_amounts.Sum());
1818 }
1919 }
Contracts/IBilling.csadded
1+using System.Collections.Generic;
2+using System.Threading.Tasks;
3+
4+namespace Shop
5+{
6+ public interface IBilling
7+ {
8+ /// <summary>
9+ /// The amounts billed so far.
10+ /// </summary>
11+ IReadOnlyList<decimal> Amounts { get; }
12+ /// <summary>Totals every amount.</summary>
13+ Task<decimal> TotalAsync();
14+ }
15+}

#generic-class

Gives the interface the class's type parameters and constraints, in the file-scoped namespace style of the class

success
Targetsymbol T:Shop.Repository`1
Arguments
name"IRepository"
IRepository.csadded
1+using System.Collections.Generic;
2+
3+namespace Shop;
4+
5+public interface IRepository<T> where T : class
6+{
7+ void Add(T item);
8+ IReadOnlyList<T> All();
9+}
Repository.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop;
44
5−public class Repository<T> where T : class
5+public class Repository<T> : IRepository<T> where T : class
66 {
77 private readonly List<T> _items = new List<T>();
88
99 public void Add(T item) => _items.Add(item);
1010
1111 public IReadOnlyList<T> All() => _items;
1212 }

#keeps-base-class-and-interfaces

Adds the new interface after the base class and interfaces the class already has

success
Targetsymbol T:Shop.Order
Arguments
name"IOrder"
members["Describe"]
IOrder.csadded
1+namespace Shop
2+{
3+ public interface IOrder
4+ {
5+ string Describe();
6+ }
7+}
Order.csmodified
11 using System;
22
33 namespace Shop
44 {
55 public abstract class Entity
66 {
77 public int Id;
88 }
99
10− public class Order : Entity, IComparable<Order>
10+ public class Order : Entity, IComparable<Order>, IOrder
1111 {
1212 public int CompareTo(Order other) => Id.CompareTo(other.Id);
1313
1414 public string Describe() => "Order " + Id;
1515 }
1616 }

#member-not-eligible

Refuses a member that is not public, which could not implement the interface

refusal
Targetsymbol T:Shop.Order
Arguments
name"IOrder"
members["Total", "Recalculate"]
Refusesmember-not-eligible mentioning “Recalculate”; every file is left unchanged
Order.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 public decimal Total { get; set; }
6
7 internal void Recalculate() => Total = 0;
8 }
9 }

#member-not-found

Refuses a member name the class does not declare

refusal
Targetsymbol T:Shop.Order
Arguments
name"IOrder"
members["Total", "Cancel"]
Refusesmember-not-found mentioning “Cancel”; every file is left unchanged
Order.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 public decimal Total { get; set; }
6 }
7 }

#no-members

Refuses when the class has no public instance members to declare

refusal
Targetsymbol T:Shop.Settings
Arguments
name"ISettings"
Refusesno-members; every file is left unchanged
Settings.csinput
1 namespace Shop
2 {
3 public class Settings
4 {
5 public static int Retries = 3;
6
7 private int _timeout;
8
9 public static int Timeout() => Retries * 10;
10 }
11 }

#nullable-annotations

Keeps nullable annotations in the signatures

success
Targetsymbol T:Shop.Directory
Arguments
name"IDirectory"
Projectnullable enable
Directory.csmodified
11 namespace Shop
22 {
3− public class Directory
3+ public class Directory : IDirectory
44 {
55 public string? Find(string? name) => name;
66 }
77 }
IDirectory.csadded
1+namespace Shop
2+{
3+ public interface IDirectory
4+ {
5+ string? Find(string? name);
6+ }
7+}

#overloads

Takes every overload of a named method

success
Targetsymbol T:Shop.Cart
Arguments
name"ICart"
members["Add"]
Cart.csmodified
11 namespace Shop
22 {
3− public class Cart
3+ public class Cart : ICart
44 {
55 public int Count { get; private set; }
66
77 public void Add(string item) => Count++;
88
99 public void Add(string item, int quantity) => Count += quantity;
1010 }
1111 }
ICart.csadded
1+namespace Shop
2+{
3+ public interface ICart
4+ {
5+ void Add(string item);
6+ void Add(string item, int quantity);
7+ }
8+}

#selected-members

Declares the chosen members in a new interface beside the class and makes the class implement it

success
Targetsymbol T:Shop.Order
Arguments
name"IOrder"
members["Total", "Add"]
IOrder.csadded
1+namespace Shop
2+{
3+ public interface IOrder
4+ {
5+ decimal Total { get; }
6+ void Add(decimal amount);
7+ }
8+}
Order.csmodified
11 namespace Shop
22 {
3− public class Order
3+ public class Order : IOrder
44 {
55 public decimal Total { get; private set; }
66
77 public void Add(decimal amount)
88 {
99 Total += amount;
1010 Log(amount);
1111 }
1212
1313 public void Clear() => Total = 0;
1414
1515 private void Log(decimal amount)
1616 {
1717 }
1818 }
1919 }

#type-already-exists

Refuses an interface name the namespace already uses

refusal
Targetsymbol T:Shop.Order
Arguments
name"IOrder"
file"Contracts.cs"
Refusestype-already-exists mentioning “Shop.IOrder”; every file is left unchanged
Order.csinput
1 namespace Shop
2 {
3 public interface IOrder
4 {
5 }
6
7 public class Order
8 {
9 public decimal Total { get; set; }
10 }
11 }