Replaces a constructor that only hands its parameters to fields and properties with a C# 12 primary constructor. Private fields that merely hold a parameter are removed and their uses read the parameter directly; other members are initialized from it.
Arguments
None. The target is the class or struct, by symbol.
Precondition
The type is a class or struct, not a record, and the project's language version is C# 12 or later. It has exactly one instance constructor, which is public, carries no attributes or documentation comment, and does not call this(...). The constructor body only assigns parameters to fields or auto-properties of the type, one statement each, and every parameter is either assigned or passed to the base constructor. After the change, every use of a removed field reads the parameter; none would bind to a local, a parameter or a member of the same name.
The constructor's parameter list, with defaults and modifiers, follows the type's name and type parameters. A base(...) call becomes the argument list of the base type in the base list. The constructor is removed. A field is removed and its uses, _field or this._field, become the parameter when it is private, has no attributes or comments, is assigned only in the constructor, is read only through this, and its parameter is assigned to nothing else and not passed to the base constructor. Every other assigned member keeps its declaration and gains the parameter as its initializer.
Preserved
Construction and the value of every member. A captured parameter is mutable where a readonly field was not, but nothing assigned it before.
Limitations
A class with several constructors is refused rather than having the others chain to the primary one. A captured parameter no longer has the field's name in debuggers and reflection.
Error codes
Code Meaning unsupported-typethe type is a record, an interface or already has a primary constructor language-versionthe project's language version is older than C# 12 several-constructorsthe type has more than one instance constructor, or none constructor-accessibilitythe constructor is not public constructor-annotatedthe constructor has attributes or a documentation comment constructor-has-logicthe constructor does more than assign its parameters to fields and auto-properties parameter-shadoweda use of a removed field would bind to another symbol with the parameter's name
Cases
Expand all · Collapse all
Diffs show each file from before to after .
/*[*/ … /*]*/ marks a selection and /*^*/ a caret; the runner removes them before the refactoring runs.
# base-constructor-argumentsMoves the base constructor call onto the base type in the base list
success
Target symbol T:Shop.Discount
Discount.cs modified
1 1 namespace Shop ;2 2 3 3 public abstract class Adjustment 4 4 { 5 5 protected Adjustment (decimal amount) 6 6 { 7 7 Amount = amount; 8 8 } 9 9 10 10 public decimal Amount { get ; } 11 11 } 12 12 13 − public sealed class Discount : Adjustment 13 + public sealed class Discount (decimal amount, string code) : Adjustment (-amount)14 14 { 15 − private readonly string _code; 16 − 17 − public Discount (decimal amount, string code) : base (-amount) 18 − { 19 − _code = code; 20 − } 21 − 22 − public string Describe () => _code + ": " + Amount ; 15 + public string Describe () => code + ": " + Amount ; 23 16 }
View fixture on GitHub
# constructor-has-logic-rejectedRefuses a constructor that validates its argument as well as assigning it
refusal
Target symbol T:Shop.Money
Refuses constructor-has-logic; every file is left unchanged
Money.cs input
1 using System ;2 3 namespace Shop ;4 5 public sealed class Money 6 { 7 private readonly decimal _amount; 8 9 public Money (decimal amount) 10 { 11 if (amount < 0 ) 12 throw new ArgumentOutOfRangeException (nameof (amount)); 13 14 _amount = amount; 15 } 16 17 public decimal Amount => _amount; 18 }
View fixture on GitHub
# documented-constructor-rejectedRefuses a constructor with a documentation comment, which a primary constructor has nowhere to keep
refusal
Target symbol T:Shop.Greeter
Refuses constructor-annotated; every file is left unchanged
Greeter.cs input
1 namespace Shop ;2 3 public class Greeter 4 { 5 private readonly string _greeting; 6 7 /// <summary>Creates a greeter.</summary> 8 /// <param name="greeting">The word to greet with.</param> 9 public Greeter (string greeting) 10 { 11 _greeting = greeting; 12 } 13 14 public string Greet (string name) => _greeting + ", " + name; 15 }
View fixture on GitHub
# field-read-from-another-instance-is-keptKeeps a field that another instance's member reads, initializing it from the parameter, and keeps a commented field too
success
Target symbol T:Shop.Money
Money.cs modified
1 1 namespace Shop ;2 2 3 − public sealed class Money 3 + public sealed class Money (decimal amount, string currency)4 4 { 5 − private readonly decimal _amount; 5 + private readonly decimal _amount = amount; 6 6 7 7 // ISO 4217 code. 8 − private readonly string _currency; 9 − 10 − public Money (decimal amount, string currency) 11 − { 12 − _amount = amount; 13 − _currency = currency; 14 − } 8 + private readonly string _currency = currency; 15 9 16 10 public bool IsMore (Money other) => _amount > other._amount; 17 11 18 12 public string Currency () => _currency; 19 13 }
View fixture on GitHub
# fields-become-captured-parametersReplaces a constructor that only assigns private readonly fields with a primary constructor whose parameters the members use directly
success
Target symbol T:Shop.OrderService
OrderService.cs modified
1 1 namespace Shop ;2 2 3 3 /// <summary>Places orders.</summary> 4 − public class OrderService 4 + public class OrderService (IRepository repository, ILogger logger)5 5 { 6 − private readonly IRepository _repository; 7 − private readonly ILogger _logger; 8 − 9 − public OrderService (IRepository repository, ILogger logger) 10 − { 11 − _repository = repository; 12 − this ._logger = logger; 13 − } 14 − 15 6 public void Place (string order) 16 7 { 17 8 // Save first so a failed log still keeps the order. 18 − _repository.Save (order); 19 − this ._logger.Log ("placed " + order); 9 + repository.Save (order); 10 + logger.Log ("placed " + order); 20 11 } 21 12 }
Services.cs unchanged
1 1 namespace Shop ;2 2 3 3 public interface IRepository 4 4 { 5 5 void Save (string order); 6 6 } 7 7 8 8 public interface ILogger 9 9 { 10 10 void Log (string message); 11 11 } 12 12 13 13 public static class Composition 14 14 { 15 15 public static OrderService Create (IRepository repository, ILogger logger) => new OrderService (repository, logger: logger); 16 16 }
View fixture on GitHub
# generic-classPuts the parameters of a generic class after its type parameters and before its constraint
success
Target symbol T:Shop.Shelf`1
Shelf.cs modified
1 1 using System .Collections .Generic ;2 2 3 3 namespace Shop ;4 4 5 − public class Shelf <T > where T : class 5 + public class Shelf <T >(List <T > items) where T : class 6 6 { 7 − private readonly List <T > _items; 8 − 9 − public Shelf (List <T > items) 10 − { 11 − _items = items; 12 − } 13 − 14 − public T First () => _items.Count > 0 ? _items[0 ] : null ; 7 + public T First () => items.Count > 0 ? items[0 ] : null ; 15 8 }
View fixture on GitHub
# language-version-rejectedRefuses when the project's language version predates primary constructors on classes
refusal
Target symbol T:Shop.Greeter
Project langVersion 11
Refuses language-version; every file is left unchanged
Greeter.cs input
1 namespace Shop ;2 3 public class Greeter 4 { 5 private readonly string _greeting; 6 7 public Greeter (string greeting) 8 { 9 _greeting = greeting; 10 } 11 12 public string Greet (string name) => _greeting + ", " + name; 13 }
View fixture on GitHub
# non-public-constructor-rejectedRefuses a private constructor, since a primary constructor is public
refusal
Target symbol T:Shop.Token
Refuses constructor-accessibility; every file is left unchanged
Token.cs input
1 namespace Shop ;2 3 public class Token 4 { 5 private readonly string _value; 6 7 private Token (string value ) 8 { 9 _value = value ; 10 } 11 12 public static Token Create () => new Token ("t" ); 13 14 public string Value => _value; 15 }
View fixture on GitHub
# parameter-shadowed-rejectedRefuses when a method parameter has the name the captured parameter would take, so the field's uses would bind to it
refusal
Target symbol T:Shop.Greeter
Refuses parameter-shadowed mentioning “greeting”; every file is left unchanged
Greeter.cs input
1 namespace Shop ;2 3 public class Greeter 4 { 5 private readonly string _greeting; 6 7 public Greeter (string greeting) 8 { 9 _greeting = greeting; 10 } 11 12 public string Greet (string greeting) => _greeting + ", " + greeting; 13 }
View fixture on GitHub
# property-gets-initializerInitializes a get-only property from its parameter and captures the parameter behind a private field, on C# 12
success
Target symbol T:Shop.Customer
Project langVersion 12
Customer.cs modified
1 1 namespace Shop ;2 2 3 − public class Customer 3 + public class Customer (string name, decimal limit)4 4 { 5 − private readonly decimal _limit; 5 + public string Name { get ; } = name; 6 6 7 − public Customer (string name, decimal limit) 8 − { 9 − Name = name; 10 − _limit = limit; 11 − } 12 − 13 − public string Name { get ; } 14 − 15 − public bool CanSpend (decimal amount) => amount <= _limit; 7 + public bool CanSpend (decimal amount) => amount <= limit; 16 8 }
View fixture on GitHub
# record-rejectedRefuses a record, whose parameter list would declare properties rather than plain parameters
refusal
Target symbol T:Shop.Point
Refuses unsupported-type; every file is left unchanged
Point.cs input
1 namespace Shop ;2 3 public record Point 4 { 5 public Point (int x) 6 { 7 X = x; 8 } 9 10 public int X { get ; } 11 }
View fixture on GitHub
# several-constructors-rejectedRefuses a class with more than one constructor
refusal
Target symbol T:Shop.Range
Refuses several-constructors; every file is left unchanged
Range.cs input
1 namespace Shop ;2 3 public class Range 4 { 5 private readonly int _low; 6 private readonly int _high; 7 8 public Range (int low, int high) 9 { 10 _low = low; 11 _high = high; 12 } 13 14 public Range (int high) : this (0 , high) 15 { 16 } 17 18 public int Length => _high - _low; 19 }
View fixture on GitHub
# structGives a readonly struct a primary constructor that initializes its property
success
Target symbol T:Shop.Temperature
Temperature.cs modified
1 1 namespace Shop ;2 2 3 − public readonly struct Temperature 3 + public readonly struct Temperature (double celsius)4 4 { 5 − public Temperature (double celsius) 6 − { 7 − Celsius = celsius; 8 − } 9 − 10 − public double Celsius { get ; } 5 + public double Celsius { get ; } = celsius; 11 6 12 7 public double Fahrenheit => Celsius * 9 / 5 + 32 ; 13 8 }
View fixture on GitHub