RefactorMCP catalog

Replace Constructor with Factory Method

Adds a static factory method that calls a constructor, narrows the constructor, and makes every creation in the solution call the factory.

Arguments

ArgumentMeaning
namethe factory method's name; Create when omitted
accessibilitythe constructor's new accessibility; private when omitted

The target is the constructor, by symbol, which tells overloads apart.

Precondition

  • The constructor belongs to a class that is not abstract or static.
  • The accessibility is one of the six C# accessibilities.
  • The type has no member with the factory's name, other than methods whose parameters differ.
  • No creation sets members with an object or collection initializer.
  • Everything else that calls the constructor directly, such as a derived class's base(...) or a new() constraint, can still reach it with the new accessibility.

Transformation

  • The factory, static, with the constructor's former accessibility, its parameters (types, modifiers and defaults) and its type as the return type, goes after the constructor and returns new Type(arguments).
  • The constructor's accessibility becomes the requested one; its documentation and other modifiers stay.
  • Every new Type(...) and target-typed new(...) calling the constructor, outside the factory, becomes Type.Name(...) with the same arguments, including their names, and the same trivia. Constructed generic types keep their type arguments, as in Box<int>.Create(5).
  • Calls of other overloads and this(...) or base(...) calls are left alone.

Preserved

  • Every creation still runs the same constructor with the same arguments.

Limitations

  • Creations through reflection, Activator.CreateInstance or a new() generic constraint are not rewritten; the last is refused by the compile check when the constructor becomes inaccessible.

Error codes

CodeMeaning
invalid-accessibilitythe accessibility argument is not a C# accessibility
abstract-typethe constructor belongs to an abstract or static class
name-conflictthe type already has a member with the factory's name and parameters
object-initializera creation uses an object or collection initializer
constructor-still-neededa derived class or constraint needs the constructor at its old accessibility

Cases

·

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

#abstract-type-rejected

Refuses a constructor of an abstract class, which a factory method cannot instantiate

refusal
Targetsymbol M:Shop.Adjustment.#ctor(System.Decimal)
Refusesabstract-type; every file is left unchanged
Adjustment.csinput
1 namespace Shop;
2
3 public abstract class Adjustment
4 {
5 protected Adjustment(decimal amount)
6 {
7 Amount = amount;
8 }
9
10 public decimal Amount { get; }
11 }

#calls-in-another-file

Adds a static Create method after the constructor, makes the constructor private and calls Create wherever the constructor was called

success
Targetsymbol M:Shop.Order.#ctor(System.String,System.Int32)
Checkout.csmodified
11 namespace Shop;
22
33 public class Checkout
44 {
5− public Order Single(string customer) => new Order(customer, 1);
5+ public Order Single(string customer) => Order.Create(customer, 1);
66
77 public Order Bulk(string customer)
88 {
99 // Bulk orders are always a dozen.
10− return new Order(quantity: 12, customer: customer);
10+ return Order.Create(quantity: 12, customer: customer);
1111 }
1212 }
Order.csmodified
11 namespace Shop;
22
33 public class Order
44 {
5− public Order(string customer, int quantity)
5+ private Order(string customer, int quantity)
66 {
77 Customer = customer;
88 Quantity = quantity;
99 }
1010
11+ public static Order Create(string customer, int quantity) => new Order(customer, quantity);
12+
1113 public string Customer { get; }
1214
1315 public int Quantity { get; }
1416 }

#derived-class-rejected

Refuses to make the constructor private while a derived class calls it

refusal
Targetsymbol M:Shop.Notice.#ctor(System.String)
Refusesconstructor-still-needed mentioning “Alert.cs”; every file is left unchanged
Alert.csinput
1 namespace Shop;
2
3 public class Alert : Notice
4 {
5 public Alert(string text) : base(text.ToUpperInvariant())
6 {
7 }
8 }
Notice.csinput
1 namespace Shop;
2
3 public class Notice
4 {
5 public Notice(string text)
6 {
7 Text = text;
8 }
9
10 public string Text { get; }
11 }

#generic-type

Calls the factory on the constructed type, whether its type argument is concrete or a type parameter

success
Targetsymbol M:Shop.Box`1.#ctor(`0)
Box.csmodified
11 namespace Shop;
22
33 public class Box<T>
44 {
5− public Box(T value)
5+ private Box(T value)
66 {
77 Value = value;
88 }
9+
10+ public static Box<T> Create(T value) => new Box<T>(value);
911
1012 public T Value { get; }
1113 }
1214
1315 public static class Boxes
1416 {
15− public static Box<int> Five() => new Box<int>(5);
17+ public static Box<int> Five() => Box<int>.Create(5);
1618
17− public static Box<TItem> Of<TItem>(TItem item) => new Box<TItem>(item);
19+ public static Box<TItem> Of<TItem>(TItem item) => Box<TItem>.Create(item);
1820 }

#invalid-accessibility-rejected

Refuses an accessibility that is not a C# accessibility

refusal
Targetsymbol M:Shop.Order.#ctor(System.String)
Arguments
accessibility"hidden"
Refusesinvalid-accessibility; every file is left unchanged
Order.csinput
1 namespace Shop;
2
3 public class Order
4 {
5 public Order(string customer)
6 {
7 Customer = customer;
8 }
9
10 public string Customer { get; }
11 }

#name-conflict-rejected

Refuses when the type already has a method with the factory's name and parameters

refusal
Targetsymbol M:Shop.Order.#ctor(System.String)
Refusesname-conflict; every file is left unchanged
Order.csinput
1 namespace Shop;
2
3 public class Order
4 {
5 public Order(string customer)
6 {
7 Customer = customer;
8 }
9
10 public string Customer { get; }
11
12 public static Order Create(string customer) => new Order(customer.Trim());
13 }

#object-initializer-rejected

Refuses when a call sets properties with an object initializer, which a method call cannot carry

refusal
Targetsymbol M:Shop.Order.#ctor(System.String)
Refusesobject-initializer; every file is left unchanged
Order.csinput
1 namespace Shop;
2
3 public class Order
4 {
5 public Order(string customer)
6 {
7 Customer = customer;
8 }
9
10 public string Customer { get; }
11
12 public string Note { get; set; } = "";
13
14 public static Order Gift(string customer) => new Order(customer) { Note = "gift" };
15 }

#one-overload

Replaces only the calls of the chosen overload; the other constructor, which chains to it, is untouched

success
Targetsymbol M:Shop.Money.#ctor(System.Decimal,System.String)
Arguments
name"Of"
Money.csmodified
11 namespace Shop;
22
33 public class Money
44 {
55 public Money(decimal amount) : this(amount, "EUR")
66 {
77 }
88
9− public Money(decimal amount, string currency)
9+ private Money(decimal amount, string currency)
1010 {
1111 Amount = amount;
1212 Currency = currency;
1313 }
14+
15+ public static Money Of(decimal amount, string currency) => new Money(amount, currency);
1416
1517 public decimal Amount { get; }
1618
1719 public string Currency { get; }
1820
1921 public static Money Zero() => new Money(0m);
2022
21− public static Money Dollars(decimal amount) => new Money(amount, "USD");
23+ public static Money Dollars(decimal amount) => Money.Of(amount, "USD");
2224 }

#protected-for-derived-class

Makes the constructor protected so a derived class can still call it, keeping its documentation and default value

success
Targetsymbol M:Shop.Notice.#ctor(System.String,System.Int32)
Arguments
accessibility"protected"
Board.csmodified
11 namespace Shop;
22
33 public static class Board
44 {
5− public static Notice Welcome() => new Notice("Welcome");
5+ public static Notice Welcome() => Notice.Create("Welcome");
66 }
Notice.csmodified
11 namespace Shop;
22
33 public class Notice
44 {
55 /// <summary>Creates a notice.</summary>
6− public Notice(string text, int priority = 0)
6+ protected Notice(string text, int priority = 0)
77 {
88 Text = text;
99 Priority = priority;
1010 }
11+
12+ public static Notice Create(string text, int priority = 0) => new Notice(text, priority);
1113
1214 public string Text { get; }
1315
1416 public int Priority { get; }
1517 }
1618
1719 public class Alert : Notice
1820 {
1921 public Alert(string text) : base(text, 9)
2022 {
2123 }
2224 }

#target-typed-new

Replaces target-typed new expressions, naming the type they create

success
Targetsymbol M:Shop.Sku.#ctor(System.String)
Arguments
name"Parse"
Sku.csmodified
11 using System.Collections.Generic;
22
33 namespace Shop;
44
55 public sealed class Sku
66 {
7− public Sku(string code)
7+ private Sku(string code)
88 {
99 Code = code;
1010 }
1111
12+ public static Sku Parse(string code) => new Sku(code);
13+
1214 public string Code { get; }
1315
14− public static List<Sku> Defaults() => new() { new("A1"), new("B2") };
16+ public static List<Sku> Defaults() => new() { Sku.Parse("A1"), Sku.Parse("B2") };
1517 }