RefactorMCP catalog

Move Property

Moves a property to another type. An instance property moves onto the type of a 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 property moves to a named type.

Arguments

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

Precondition

  • The property is not an indexer, and is not virtual, abstract, an override or an interface implementation.
  • An instance property's accessors use nothing of its class except the via, since a property cannot take the instance as a parameter. An auto-property qualifies.
  • The target and via meet the same conditions as for Move Field.
  • Every use can access the via, and none sets the property 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.
  • The accessors may use other members of the property's class; they reach them through the holder.

Transformation

  • The property, with its accessors, initializer, documentation and comments, is appended to the target type. A private property becomes internal.
  • In its accessors, uses of the via become this: _address.City becomes City.
  • Every use goes through the via (Street becomes Address.Street, customer.Street becomes customer.Address.Street), or, for a static property, names the target type.
  • With into, the property keeps its accessibility and is appended to the holding class. Its accessors reach the rest of its old class through the holder (Number becomes Address.Number), and every use through the holder becomes direct: customer.Address.Full becomes customer.Full.

Preserved

  • Reads and writes of the property, through the via.
  • The property's type, accessors and initializer.

Limitations

  • A property with a backing field in its class must have the field moved first; its accessors use that field, so the move is refused.
  • The via must hold the same object whenever the property is used.

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 property 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 property's own class
member-existsthe target already has a member with the property's name
polymorphic-methodthe property is virtual, abstract, an override or implements an interface
uses-source-membersthe accessors use other members of the property's class
object-initializeran object initializer sets the property
via-not-accessiblea use of the property cannot access the via
conditional-accessa use 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.

#auto-property-via

Moves an auto-property onto the type of a property; every use, here and in other files, goes through it

success
Targetsymbol P: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+ /// <summary>The first line of the address.</summary>
8+ public string Street { get; set; }
69 }
710 }
Courier.csmodified
11 namespace Shop
22 {
33 public class Courier
44 {
5− public void Deliver(Customer customer) => customer.Street = customer.Street.Trim();
5+ public void Deliver(Customer customer) => customer.Address.Street = customer.Address.Street.Trim();
66 }
77 }
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 public Address Address { get; } = new Address();
66
7− /// <summary>The first line of the address.</summary>
8− public string Street { get; set; }
9−
10− public string Label() => Street + ", " + Address.Town;
7+ public string Label() => Address.Street + ", " + Address.Town;
118 }
129 }

#computed-uses-target

A computed property that reads the target's members moves and reads them directly

success
Targetsymbol P:Shop.Customer.Town
Arguments
via"_address"
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 private readonly Address _address = new Address();
66
7− public string Town => _address.City.ToUpperInvariant();
8−
9− public string Label() => "Town: " + Town;
7+ public string Label() => "Town: " + _address.Town;
108 }
119
1210 public class Address
1311 {
1412 public string City { get; set; }
13+
14+ public string Town => City.ToUpperInvariant();
1515 }
1616 }

#into-holder

Into the class holding the property's class: its accessors reach the rest of its old class through the holder, and uses through the holder, here in another file, become direct

success
Targetsymbol P:Shop.Address.Full
Arguments
into"Customer"
Address.csmodified
11 namespace Shop
22 {
33 public class Address
44 {
55 public string Street { get; set; }
66
77 public int Number { get; set; }
8−
9− public string Full => Number + " " + Street;
108 }
119 }
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 public Address Address { get; } = new Address();
6+
7+ public string Full => Address.Number + " " + Address.Street;
68 }
79 }
Labels.csmodified
11 namespace Shop
22 {
33 public class Labels
44 {
5− public string For(Customer customer) => customer.Address.Full.ToUpper();
5+ public string For(Customer customer) => customer.Full.ToUpper();
66 }
77 }

#into-holder-not-found-rejected

Refuses a class that holds no instance of the property's class

refusal
Targetsymbol P:Shop.Address.Street
Arguments
into"Order"
Refusesholder-not-found; every file is left unchanged
Address.csinput
1 namespace Shop
2 {
3 public class Address
4 {
5 public string Street { get; set; }
6 }
7
8 public class Order
9 {
10 public int Id { get; set; }
11 }
12 }

#into-several-holders-rejected

Refuses a class that holds the property's class in several fields, each of which would need its own copy

refusal
Targetsymbol P:Shop.Address.Street
Arguments
into"Customer"
Refusesseveral-holders; every file is left unchanged
Address.csinput
1 namespace Shop
2 {
3 public class Address
4 {
5 public string Street { get; set; }
6 }
7
8 public class Customer
9 {
10 private readonly Address _home = new Address();
11 private readonly Address _work = new Address();
12
13 public string Streets() => _home.Street + " / " + _work.Street;
14 }
15 }

#no-reference-to-target-rejected

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

refusal
Targetsymbol P: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 public string Street { get; set; }
6 }
7
8 public class Address
9 {
10 }
11 }

#object-initializer-rejected

Refuses while an object initializer sets the property

refusal
Targetsymbol P:Shop.Customer.Street
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 Street { get; set; }
8
9 public static Customer Create() => new Customer { Street = "High Street" };
10 }
11
12 public class Address
13 {
14 }
15 }

#static-property

A static property moves to the named type, and uses name that type

success
Targetsymbol P:Shop.Order.Currency
Arguments
to"Settings"
Order.csmodified
11 namespace Shop
22 {
33 public class Order
44 {
5− public static string Currency { get; set; } = "GBP";
6−
7− public string Price(decimal amount) => amount + " " + Currency;
5+ public string Price(decimal amount) => amount + " " + Settings.Currency;
86 }
97
108 public static class Settings
119 {
10+ public static string Currency { get; set; } = "GBP";
1211 }
1312 }

#uses-source-members-rejected

Refuses a property whose accessors use other members of its class, since a property cannot take the instance as a parameter

refusal
Targetsymbol P:Shop.Customer.Label
Arguments
via"_address"
Refusesuses-source-members; 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 Name { get; set; }
8
9 public string Label => Name + ", " + _address.City;
10 }
11
12 public class Address
13 {
14 public string City { get; set; }
15 }
16 }