RefactorMCP catalog

Move Field

Moves a field to another type. An instance field moves onto the type of another field or property of its class (the "via") and is reached through it, or into the class that holds its class (into), whose uses went through that holder; a static field or constant moves to a named type.

Arguments

ArgumentMeaning
target.symbolthe field, such as F:Shop.Customer._street
viafor an instance field: the field or property to move through, such as _address
tothe target type: required for a static field or constant; for an instance field, the one field or property of that type is used
intoinstead of via or to, for an instance field: the class that holds the field's class in a field or property, such as Customer

Composite recipes use the via form, for example { "refactoring": "move-field", "target": { "symbol": "F:Shop.Customer.Street" }, "arguments": { "via": "_address" } }, and the into form to move a field back towards the class that holds its own, as in { "refactoring": "move-field", "target": { "symbol": "F:Shop.Address._country" }, "arguments": { "into": "Customer" } }.

Precondition

  • An instance field has a via: a field or property of its class whose type is a class declared in the solution, other than the field's own class. With to, exactly one field or property has that type.
  • A static field or constant has a target type declared in the solution.
  • The target has no member with the field's name.
  • An instance field that is readonly is not assigned outside its initializer, since the assignment could not go through the via.
  • Every use of an instance field can access the via, and none sets it in an object initializer or uses null-conditional access.

With into:

  • The member is an instance member. The into class, declared in the solution, has exactly one field or property whose type is the member's class: the holder.
  • The holder is an instance field, or a get-only auto-property, that creates its object in its initializer and is never assigned. Each instance of the holding class then has exactly one instance of the member's class, from the start, whose state the member can take over.
  • Every use of the member goes through the holder: _address.Street, this._address.Street or customer.Address.Street. A use inside its own class, or through any other instance, would have no holder to go through.
  • The holding class has no member with the member's name.

Transformation

  • The field, with its initializer and the comments above it, is appended to the target type. A private field becomes internal so its old class can still reach it. A field declared alongside others is split out of its declaration.
  • Every use of an instance field goes through the via: _street becomes _address._street, this._street becomes this._address._street, and customer.Postcode in another file becomes customer.Address.Postcode.
  • Every use of a static field or constant names the target type: DefaultQuantity becomes Defaults.DefaultQuantity.
  • With into, the field keeps its accessibility and goes after the holding class's last field, and every use through the holder becomes direct: _address.city becomes city, or this.city where a local or parameter of that name hides it; this._address.city becomes this.city; and customer.Address.city becomes customer.city.

Preserved

  • The field's type, nullable annotation, modifiers other than accessibility, and initializer.
  • Reads and writes of the field, through the via.

Limitations

  • The via must hold the same object whenever the field is used. The move does not check that the via is assigned before the field is first used, for example in a constructor that sets the field before the via.
  • The field is always appended at the end of the target type, except with into.
  • With into, the field's initializer runs among the holding class's field initializers rather than when the holder creates its object.

Error codes

CodeMeaning
no-reference-to-targetno field or property of the class has the to type
ambiguous-targetseveral fields or properties have the to type
via-not-foundno field or property has the via's name
static-member-viaa static field was given a via; name the target type instead
target-not-in-sourcethe target type is not declared in the solution
target-not-classthe via's type is not a class
same-typethe target is the field's own class
member-existsthe target already has a member with the field's name
readonly-assigneda readonly instance field is assigned outside its initializer
object-initializeran object initializer sets the field
via-not-accessiblea use of the field cannot access the via
conditional-accessa use of the field goes through null-conditional access
holder-not-foundwith into, the class has no field or property of the member's class
several-holderswith into, the class has several fields or properties of the member's class
holder-not-createdwith into, the holder is static, does not create its object in its initializer, or is assigned
used-outside-holderwith into, a use of the member does not go through the holder

Cases

·

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

#constant

A private constant moves as internal, since its old class still uses it

success
Targetsymbol F:Shop.Order.Rate
Arguments
to"TaxRules"
Order.csmodified
11 namespace Shop
22 {
33 public class Order
44 {
5− private const decimal Rate = 0.2m;
6−
75 public decimal Net { get; set; }
86
9− public decimal Tax() => Net * Rate;
7+ public decimal Tax() => Net * TaxRules.Rate;
108 }
119
1210 public static class TaxRules
1311 {
12+ internal const decimal Rate = 0.2m;
1413 }
1514 }

#into-holder

Into the class holding the field's class: the field joins its fields, keeping its accessibility and comments, and uses through the holder become direct, with this where a parameter hides it

success
Targetsymbol F:Shop.Address.city
Arguments
into"Customer"
Address.csmodified
11 namespace Shop
22 {
33 public class Address
44 {
5− // Where the customer lives.
6− public string city = "London";
7−
85 public string Street { get; set; }
96 }
107 }
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 private readonly Address _address = new Address();
6+ // Where the customer lives.
7+ public string city = "London";
68
79 public string Name { get; set; }
810
911 public void MoveTo(string city)
1012 {
11− _address.city = city;
13+ this.city = city;
1214 }
1315
14− public string Label() => Name + ", " + this._address.city;
16+ public string Label() => Name + ", " + this.city;
1517 }
1618 }

#into-holder-not-created-rejected

Refuses a holder that is assigned in a constructor, since its object may then be shared or replaced

refusal
Targetsymbol F:Shop.Address.city
Arguments
into"Customer"
Refusesholder-not-created; every file is left unchanged
Address.csinput
1 namespace Shop
2 {
3 public class Address
4 {
5 public string city;
6 }
7
8 public class Customer
9 {
10 private readonly Address _address;
11
12 public Customer(Address address)
13 {
14 _address = address;
15 }
16
17 public string City() => _address.city;
18 }
19 }

#into-used-outside-holder-rejected

Refuses to move into the holding class a field its own class still uses

refusal
Targetsymbol F:Shop.Address._country
Arguments
into"Customer"
Refusesused-outside-holder mentioning “Address.cs”; every file is left unchanged
Address.csinput
1 namespace Shop
2 {
3 public class Address
4 {
5 internal string _country = "UK";
6
7 public string Format() => "Somewhere, " + _country;
8 }
9 }
Customer.csinput
1 namespace Shop
2 {
3 public class Customer
4 {
5 private readonly Address _address = new Address();
6
7 public string Country() => _address._country;
8 }
9 }

#member-exists-rejected

Refuses when the target already has a member with the field's name

refusal
Targetsymbol F:Shop.Customer.Town
Arguments
via"Address"
Refusesmember-exists; every file is left unchanged
Customer.csinput
1 namespace Shop
2 {
3 public class Customer
4 {
5 public Address Address { get; } = new Address();
6
7 public string Town;
8 }
9
10 public class Address
11 {
12 public string Town { get; set; }
13 }
14 }

#no-reference-to-target-rejected

Refuses an instance field when no field or property of the target type leads to it

refusal
Targetsymbol F:Shop.Customer._street
Arguments
to"Address"
Refusesno-reference-to-target; every file is left unchanged
Customer.csinput
1 namespace Shop
2 {
3 public class Customer
4 {
5 private string _street = "";
6
7 public string Street() => _street;
8 }
9
10 public class Address
11 {
12 }
13 }

#nullable

A nullable field keeps its annotation

success
Targetsymbol F:Shop.Customer._note
Arguments
via"_address"
Projectnullable enable
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 private readonly Address _address = new Address();
6− private string? _note;
76
8− public string Note() => _note ?? "none";
7+ public string Note() => _address._note ?? "none";
98
10− public void Annotate(string? note) => _note = note;
9+ public void Annotate(string? note) => _address._note = note;
1110 }
1211
1312 public class Address
1413 {
14+ internal string? _note;
1515 }
1616 }

#object-initializer-rejected

Refuses while an object initializer sets the field, which cannot set it through another member

refusal
Targetsymbol F:Shop.Customer.Postcode
Arguments
via"Address"
Refusesobject-initializer; every file is left unchanged
Customer.csinput
1 namespace Shop
2 {
3 public class Customer
4 {
5 public Address Address { get; } = new Address();
6
7 public string Postcode;
8
9 public static Customer Create() => new Customer { Postcode = "AB1" };
10 }
11
12 public class Address
13 {
14 }
15 }

#readonly-assigned-rejected

Refuses a readonly field its class's constructor assigns, since the assignment could not go through the via

refusal
Targetsymbol F:Shop.Customer._street
Arguments
via"_address"
Refusesreadonly-assigned; every file is left unchanged
Customer.csinput
1 namespace Shop
2 {
3 public class Customer
4 {
5 private readonly Address _address = new Address();
6 private readonly string _street;
7
8 public Customer(string street) => _street = street;
9
10 public string Street() => _street;
11 }
12
13 public class Address
14 {
15 }
16 }

#references-in-other-files

References in other files go through the via member too, which must be accessible there

success
Targetsymbol F:Shop.Customer.Postcode
Arguments
to"Address"
Courier.csmodified
11 namespace Shop
22 {
33 public class Courier
44 {
5− public string Route(Customer customer) => customer.Address.Town + " " + customer.Postcode;
5+ public string Route(Customer customer) => customer.Address.Town + " " + customer.Address.Postcode;
66
7− public void Correct(Customer customer, string postcode) => customer.Postcode = postcode.ToUpperInvariant();
7+ public void Correct(Customer customer, string postcode) => customer.Address.Postcode = postcode.ToUpperInvariant();
88 }
99 }
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 public Address Address { get; } = new Address();
6−
7− public string Postcode;
86 }
97
108 public class Address
119 {
1210 public string Town;
11+
12+ public string Postcode;
1313 }
1414 }

#split-declaration

A field declared alongside another is split out, and the other stays

success
Targetsymbol F:Shop.Customer._town
Arguments
via"_address"
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 private readonly Address _address = new Address();
6− private string _street = "", _town = "";
6+ private string _street = "";
77
8− public string Label() => _street + ", " + _town;
8+ public string Label() => _street + ", " + _address._town;
99 }
1010
1111 public class Address
1212 {
13+ internal string _town = "";
1314 }
1415 }

#static-field

A static field moves to the named type, and every use names that type

success
Targetsymbol F:Shop.Order.DefaultQuantity
Arguments
to"Defaults"
Basket.csmodified
11 namespace Shop
22 {
33 public class Basket
44 {
5− public void Reset() => Order.DefaultQuantity = 1;
5+ public void Reset() => Defaults.DefaultQuantity = 1;
66 }
77 }
Defaults.csmodified
11 namespace Shop
22 {
33 public static class Defaults
44 {
55 public const string Currency = "GBP";
6+
7+ public static int DefaultQuantity = 1;
68 }
79 }
Order.csmodified
11 namespace Shop
22 {
33 public class Order
44 {
5− public static int DefaultQuantity = 1;
6−
7− public int Quantity { get; set; } = DefaultQuantity;
5+ public int Quantity { get; set; } = Defaults.DefaultQuantity;
86 }
97 }

#static-with-via-rejected

Refuses to move a static field through an instance member; name the target type instead

refusal
Targetsymbol F:Shop.Customer.Count
Arguments
via"Address"
Refusesstatic-member-via; every file is left unchanged
Customer.csinput
1 namespace Shop
2 {
3 public class Customer
4 {
5 public static int Count;
6
7 public Address Address { get; } = new Address();
8 }
9
10 public class Address
11 {
12 }
13 }

#via-field

Moves a field, with its initializer, onto the type of another field; uses in its old class go through that field

success
Targetsymbol F:Shop.Customer._street
Arguments
via"_address"
Address.csmodified
11 namespace Shop
22 {
33 public class Address
44 {
55 public string Town { get; set; }
6+
7+ // The first line of the address.
8+ internal string _street = "";
69 }
710 }
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 private readonly Address _address = new Address();
66
7− // The first line of the address.
8− private string _street = "";
7+ public void Move(string street) => _address._street = street;
98
10− public void Move(string street) => _street = street;
11−
12− public string Label() => "Lives on " + this._street;
9+ public string Label() => "Lives on " + this._address._street;
1310 }
1411 }

#via-not-accessible-rejected

Refuses when code outside the class uses the field but cannot reach the private field it would go through

refusal
Targetsymbol F:Shop.Customer.Postcode
Arguments
via"_address"
Refusesvia-not-accessible; every file is left unchanged
Customer.csinput
1 namespace Shop
2 {
3 public class Customer
4 {
5 private readonly Address _address = new Address();
6
7 public string Postcode;
8
9 public Address Home() => _address;
10 }
11
12 public class Address
13 {
14 }
15
16 public class Courier
17 {
18 public string Route(Customer customer) => customer.Postcode;
19 }
20 }