RefactorMCP catalog

Move Type to File

Moves a top-level type out of a file that declares several types into a new file of its own, named after the type, in the same folder and project.

Arguments

ArgumentMeaning
target.symbolthe type to move, such as T:Shop.Receipt

Precondition

  • The type is declared directly in a namespace or at the top of the file, not nested inside another type.
  • The file declares at least one other top-level type. Moving the only type is a rename of the file, which Rename File to Match Type does.
  • No file with the type's name exists beside the original.

Transformation

  • The type, with the comments and documentation above it, is removed from the original file and written to <Type>.cs. A generic type's file is named without its type parameters.
  • The new file repeats the original's header (comments and directives such as #nullable enable above the first using), its namespace declarations, block or file-scoped and nested as they were, and the usings the type needs. Usings it does not need are left out.
  • The original file drops the usings that only the moved type needed. Usings it did not need before the move are left alone.
  • Assembly attributes stay in the original file.

Preserved

  • Every reference to the type, which keeps its name and namespace.
  • Comments above the moved type travel with it; comments and #region blocks around the types that stay are left where they were.

Limitations

  • A #region that opens directly above the moved type stays in the original file, and the new file has no region.
  • When the moved type is the last in its namespace and a directive such as #region sits directly above it, the directive is lost from the original file.
  • Partial types move one part at a time, the part declared in the file.

Error codes

CodeMeaning
only-type-in-filethe type is the only top-level type in its file
file-existsa file with the type's name already exists beside the original
nested-typethe type is nested inside another type

Cases

·

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

#comments-travel

Comments and documentation above the type move with it, and those of the types left behind stay

success
Targetsymbol T:Shop.Customer
Customer.csadded
1+namespace Shop
2+{
3+ /// <summary>
4+ /// A customer who places orders.
5+ /// </summary>
6+ // Names are not yet split into first and last.
7+ public class Customer
8+ {
9+ public string Name { get; set; }
10+ }
11+}
Model.csmodified
11 using System;
22
33 namespace Shop
44 {
55 // Orders placed through the web shop.
66 public class Order
77 {
88 public DateTime Placed { get; set; }
9− }
10−
11− /// <summary>
12− /// A customer who places orders.
13− /// </summary>
14− // Names are not yet split into first and last.
15− public class Customer
16− {
17− public string Name { get; set; }
189 }
1910
2011 #region Payments
2112
2213 public class Payment
2314 {
2415 public decimal Amount { get; set; }
2516 }
2617
2718 #endregion
2819 }

#file-exists-rejected

Refuses when a file with the type's name already exists beside it

refusal
Targetsymbol T:Shop.Customer
Refusesfile-exists; every file is left unchanged
Customer.csinput
1 namespace Shop
2 {
3 public static class CustomerDefaults
4 {
5 public const string Name = "Guest";
6 }
7 }
Model.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 }
6
7 public class Customer
8 {
9 }
10 }

#first-type-file-scoped

Moves the first type out of a file with a file-scoped namespace, keeping that form in both files

success
Targetsymbol T:Geometry.Point
Point.csadded
1+namespace Geometry;
2+
3+public record Point(double X, double Y);
Shapes.csmodified
11 namespace Geometry;
2−
3−public record Point(double X, double Y);
42
53 public class Circle
64 {
75 public Point Centre { get; init; }
86
97 public double Radius { get; init; }
108 }

#generic-type

A generic type moves to a file named without its type parameters

success
Targetsymbol T:Shop.Result`1
Result.csadded
1+using System;
2+
3+namespace Shop
4+{
5+ public class Result<T>
6+ {
7+ public Result(T value, Exception error)
8+ {
9+ Value = value;
10+ Error = error;
11+ }
12+
13+ public T Value { get; }
14+
15+ public Exception Error { get; }
16+ }
17+}
Results.csmodified
1−using System;
2−
31 namespace Shop
42 {
53 public static class Results
64 {
75 public static Result<T> Ok<T>(T value) => new Result<T>(value, null);
86 }
9−
10− public class Result<T>
11− {
12− public Result(T value, Exception error)
13− {
14− Value = value;
15− Error = error;
16− }
17−
18− public T Value { get; }
19−
20− public Exception Error { get; }
21− }
227 }

#nested-namespace

A type inside nested namespace blocks keeps the whole namespace chain, and the usings declared inside it

success
Targetsymbol T:Shop.Billing.Invoice
Billing.csmodified
11 namespace Shop
22 {
33 namespace Billing
44 {
55 using System.Collections.Generic;
66
77 public class Ledger
88 {
99 public List<Invoice> Invoices { get; } = new List<Invoice>();
1010 }
11−
12− public class Invoice
13− {
14− public decimal Amount { get; set; }
15− }
1611 }
1712 }
Invoice.csadded
1+namespace Shop
2+{
3+ namespace Billing
4+ {
5+ public class Invoice
6+ {
7+ public decimal Amount { get; set; }
8+ }
9+ }
10+}

#nested-type-rejected

Refuses a nested type, which would change its name and access to its container

refusal
Targetsymbol T:Shop.Order.Line
Refusesnested-type; every file is left unchanged
Order.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 public class Line
6 {
7 }
8 }
9
10 public class Customer
11 {
12 }
13 }

#nullable-context

The file's #nullable directive is copied to the new file, so annotations mean the same there

success
Targetsymbol T:Shop.Product
Catalogue.csmodified
11 #nullable enable
22 using System.Collections.Generic;
33
44 namespace Shop
55 {
66 public class Catalogue
77 {
88 private readonly Dictionary<string, Product> _products = new Dictionary<string, Product>();
99
1010 public Product? Find(string sku) => _products.TryGetValue(sku, out var product) ? product : null;
1111 }
12−
13− public class Product
14− {
15− public string Name { get; set; } = "";
16−
17− public string? Description { get; set; }
18− }
1912 }
Product.csadded
1+#nullable enable
2+
3+namespace Shop
4+{
5+ public class Product
6+ {
7+ public string Name { get; set; } = "";
8+
9+ public string? Description { get; set; }
10+ }
11+}

#only-type-rejected

Refuses to move the only type in a file; renaming the file is the refactoring for that

refusal
Targetsymbol T:Shop.Order
Refusesonly-type-in-file; every file is left unchanged
Orders.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 }
6 }

#usings-follow-the-type

Moves the second type of a file into its own file; each file keeps only the usings its code needs

success
Targetsymbol T:Shop.Receipt
Order.csmodified
11 using System.Collections.Generic;
22 using System.Linq;
3−using System.Text;
43
54 namespace Shop
65 {
76 public class Order
87 {
98 private readonly List<decimal> _prices = new List<decimal>();
109
1110 public decimal Total() => _prices.Sum();
1211 }
13−
14− public class Receipt
15− {
16− public string Print(Order order)
17− {
18− var text = new StringBuilder();
19− text.Append("Total: ").Append(order.Total());
20− return text.ToString();
21− }
22− }
2312 }
Receipt.csadded
1+using System.Text;
2+
3+namespace Shop
4+{
5+ public class Receipt
6+ {
7+ public string Print(Order order)
8+ {
9+ var text = new StringBuilder();
10+ text.Append("Total: ").Append(order.Total());
11+ return text.ToString();
12+ }
13+ }
14+}
Checkout.csunchanged
11 namespace Shop
22 {
33 public class Checkout
44 {
55 public string Pay(Order order) => new Receipt().Print(order);
66 }
77 }