RefactorMCP catalog

Introduce Interface for Dependency

Makes a class depend on an interface instead of a concrete class: the interface is extracted from the concrete class, and the field, property or parameter holding the dependency is declared as the interface. Callers keep passing the concrete class, which now implements the interface.

Recipe

  1. extract-interface from the dependency's class: "target": { "symbol": "T:Shop.FileWriter" }, "arguments": { "name": "IWriter", "members": ["Write"] }.
  2. change-type of the field, property or parameter to the interface: "target": { "symbol": "F:Shop.Report._writer" }, "arguments": { "to": "IWriter" }.
  3. For a field or property, change-type of each constructor parameter stored in it: "target": { "symbol": "M:Shop.Report.#ctor(Shop.FileWriter)" }, "arguments": { "parameter": "writer", "to": "IWriter" }.

Arguments

ArgumentRequiredMeaning
nameyesThe interface's name
parameternoWith a method or constructor target, the parameter holding the dependency
membersnoThe members the interface declares; defaults to every public instance member of the class
filenoThe interface's file, relative to the solution; defaults to <name>.cs beside the class

The target is the field or property by symbol, "target": { "symbol": "F:Shop.Report._writer" }, or a method or constructor by symbol with parameter.

Precondition

  • The dependency's declared type is a class declared in the solution.
  • Extract Interface's precondition: the named members exist and are public instance members, and no type has the interface's name.
  • Change Type's precondition for each declaration: every use of it needs only what the interface declares, and reaches the same member through it.

Transformation

  • The interface is created beside the class, declaring the chosen members with their documentation comments, and the class implements it.
  • The field, property or parameter is declared as the interface, keeping its nullable annotation.
  • For a field or property, each constructor parameter of the same type that the constructor assigns straight to it (_writer = writer;) is declared as the interface too.

Preserved

  • Every call: the members used through the dependency are the class's own implementations of the interface.
  • Callers, which still construct and pass the concrete class.

Limitations

  • Only constructor parameters assigned directly to the field or property change with it; a parameter passed through a local or another method keeps the concrete type.
  • An existing interface is not reused; the interface is always new.

Error codes

CodeMeaning
type-not-in-sourcethe dependency's type is not a class declared in the solution
parameter-not-foundthe method has no parameter of that name
member-not-founda named member does not exist in the class (step 1)
type-already-existsa type has the interface's name (step 1)
incompatible-usea use of the dependency needs a member the interface lacks
changes-overloada use would reach a different overload through the interface

A refusal in any step leaves every file as it was, and names the step that refused.

Cases

·

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

#field-and-constructor

Given the field, extracts the interface and changes the field and the constructor parameter stored in it, with the same result as the recipe

success
Targetsymbol F:Shop.Report._writer
Arguments
name"IWriter"
members["Write"]
FileWriter.csmodified
11 using System.IO;
22
33 namespace Shop
44 {
5− public class FileWriter
5+ public class FileWriter : IWriter
66 {
77 private readonly string _path;
88
99 public FileWriter(string path)
1010 {
1111 _path = path;
1212 }
1313
1414 /// <summary>Appends the text to the file.</summary>
1515 public void Write(string text) => File.AppendAllText(_path, text);
1616
1717 public void Flush()
1818 {
1919 }
2020 }
2121 }
IWriter.csadded
1+namespace Shop
2+{
3+ public interface IWriter
4+ {
5+ /// <summary>Appends the text to the file.</summary>
6+ void Write(string text);
7+ }
8+}
Report.csmodified
11 namespace Shop
22 {
33 public class Report
44 {
55 // Where the report goes.
6− private readonly FileWriter _writer;
6+ private readonly IWriter _writer;
77
8− public Report(FileWriter writer)
8+ public Report(IWriter writer)
99 {
1010 _writer = writer;
1111 }
1212
1313 public void Print() => _writer.Write("report");
1414 }
1515 }
Program.csunchanged
11 namespace Shop
22 {
33 public static class Program
44 {
55 public static void Run()
66 {
77 var writer = new FileWriter("report.txt");
88 new Report(writer).Print();
99 writer.Flush();
1010 }
1111 }
1212 }

#incompatible-use-rejected

The report flushes its writer, which the interface does not declare, so changing the field's type refuses after the interface was extracted, and every file is put back

refusal
Targetsymbol F:Shop.Report._writer
Arguments
name"IWriter"
members["Write"]
Refusesincompatible-use mentioning “step 2 (change-type)”; every file is left unchanged
FileWriter.csinput
1 using System.IO;
2
3 namespace Shop
4 {
5 public class FileWriter
6 {
7 private readonly string _path;
8
9 public FileWriter(string path)
10 {
11 _path = path;
12 }
13
14 /// <summary>Appends the text to the file.</summary>
15 public void Write(string text) => File.AppendAllText(_path, text);
16
17 public void Flush()
18 {
19 }
20 }
21 }
Program.csinput
1 namespace Shop
2 {
3 public static class Program
4 {
5 public static void Run()
6 {
7 var writer = new FileWriter("report.txt");
8 new Report(writer).Print();
9 writer.Flush();
10 }
11 }
12 }
Report.csinput
1 namespace Shop
2 {
3 public class Report
4 {
5 // Where the report goes.
6 private readonly FileWriter _writer;
7
8 public Report(FileWriter writer)
9 {
10 _writer = writer;
11 }
12
13 public void Print()
14 {
15 _writer.Write("report");
16 _writer.Flush();
17 }
18 }
19 }

#parameter

A nullable method parameter becomes the nullable interface, which declares every public member of the class

success
Targetsymbol M:Shop.Report.Print(Shop.FileWriter)
Arguments
parameter"writer"
name"IWriter"
Projectnullable enable
FileWriter.csmodified
11 using System.IO;
22
33 namespace Shop
44 {
5− public class FileWriter
5+ public class FileWriter : IWriter
66 {
77 private readonly string _path;
88
99 public FileWriter(string path)
1010 {
1111 _path = path;
1212 }
1313
1414 /// <summary>Appends the text to the file.</summary>
1515 public void Write(string text) => File.AppendAllText(_path, text);
1616
1717 public void Flush()
1818 {
1919 }
2020 }
2121 }
IWriter.csadded
1+namespace Shop
2+{
3+ public interface IWriter
4+ {
5+ /// <summary>Appends the text to the file.</summary>
6+ void Write(string text);
7+ void Flush();
8+ }
9+}
Report.csmodified
11 namespace Shop
22 {
33 public class Report
44 {
5− public void Print(FileWriter? writer)
5+ public void Print(IWriter? writer)
66 {
77 writer?.Write("report");
88 writer?.Flush();
99 }
1010 }
1111 }

#recipe

Extracts IWriter from FileWriter and declares the report's writer field and constructor parameter as IWriter, as the recipe of primitive steps

successrecipe
Recipe
  1. extract-interface on symbol T:Shop.FileWriter
    name"IWriter"
    members["Write"]
  2. change-type on symbol F:Shop.Report._writer
    to"IWriter"
  3. change-type on symbol M:Shop.Report.#ctor(Shop.FileWriter)
    parameter"writer"
    to"IWriter"
FileWriter.csmodified
11 using System.IO;
22
33 namespace Shop
44 {
5− public class FileWriter
5+ public class FileWriter : IWriter
66 {
77 private readonly string _path;
88
99 public FileWriter(string path)
1010 {
1111 _path = path;
1212 }
1313
1414 /// <summary>Appends the text to the file.</summary>
1515 public void Write(string text) => File.AppendAllText(_path, text);
1616
1717 public void Flush()
1818 {
1919 }
2020 }
2121 }
IWriter.csadded
1+namespace Shop
2+{
3+ public interface IWriter
4+ {
5+ /// <summary>Appends the text to the file.</summary>
6+ void Write(string text);
7+ }
8+}
Report.csmodified
11 namespace Shop
22 {
33 public class Report
44 {
55 // Where the report goes.
6− private readonly FileWriter _writer;
6+ private readonly IWriter _writer;
77
8− public Report(FileWriter writer)
8+ public Report(IWriter writer)
99 {
1010 _writer = writer;
1111 }
1212
1313 public void Print() => _writer.Write("report");
1414 }
1515 }
Program.csunchanged
11 namespace Shop
22 {
33 public static class Program
44 {
55 public static void Run()
66 {
77 var writer = new FileWriter("report.txt");
88 new Report(writer).Print();
99 writer.Flush();
1010 }
1111 }
1212 }

#type-already-exists-rejected

A type with the interface's name already exists

refusal
Targetsymbol F:Shop.Report._writer
Arguments
name"IWriter"
members["Write"]
file"Writers/IWriter.cs"
Refusestype-already-exists mentioning “step 1 (extract-interface)”; every file is left unchanged
FileWriter.csinput
1 using System.IO;
2
3 namespace Shop
4 {
5 public class FileWriter
6 {
7 private readonly string _path;
8
9 public FileWriter(string path)
10 {
11 _path = path;
12 }
13
14 /// <summary>Appends the text to the file.</summary>
15 public void Write(string text) => File.AppendAllText(_path, text);
16
17 public void Flush()
18 {
19 }
20 }
21 }
IWriter.csinput
1 namespace Shop
2 {
3 public interface IWriter
4 {
5 void WriteLine(string line);
6 }
7 }
Program.csinput
1 namespace Shop
2 {
3 public static class Program
4 {
5 public static void Run()
6 {
7 var writer = new FileWriter("report.txt");
8 new Report(writer).Print();
9 writer.Flush();
10 }
11 }
12 }
Report.csinput
1 namespace Shop
2 {
3 public class Report
4 {
5 // Where the report goes.
6 private readonly FileWriter _writer;
7
8 public Report(FileWriter writer)
9 {
10 _writer = writer;
11 }
12
13 public void Print() => _writer.Write("report");
14 }
15 }

#type-not-in-source-rejected

The dependency's class comes from a library, so no interface can be extracted from it

refusal
Targetsymbol F:Shop.Report._writer
Arguments
name"IWriter"
Refusestype-not-in-source; every file is left unchanged
Report.csinput
1 using System.IO;
2
3 namespace Shop
4 {
5 public class Report
6 {
7 private readonly TextWriter _writer;
8
9 public Report(TextWriter writer)
10 {
11 _writer = writer;
12 }
13
14 public void Print() => _writer.Write("report");
15 }
16 }