RefactorMCP catalog

Convert Property to Methods

Replaces a property with a Get method and, when it has a setter, a Set method, and turns every read and write into a call.

Precondition

  • The property is not virtual, abstract, an override, an interface member or an interface implementation, since its whole hierarchy would have to change.
  • It has no init accessor, which object initialisers use and no method can stand in for.
  • Get<Name> and Set<Name>, and for an auto-property _<name>, are free in the type.
  • Every write can become a call: it is not in an object initialiser, and the value of an assignment or increment is not used.
  • A compound assignment or increment reads the property as well as writing it, so its receiver must be a plain name, such as order or this.order, rather than a call that would run twice.
  • No use is inside nameof, whose text would change.

Transformation

  • The getter becomes <Type> Get<Name>() and the setter void Set<Name>(<Type> value), in the property's place, separated by a blank line. Expression bodies stay expression bodies and block bodies stay blocks, laid out as method bodies.
  • An auto-property gets a private backing field _<name> after the type's other fields, holding the property's initialiser. It is readonly when the property has no setter, and constructors that assigned such a property assign the field instead.
  • Each method keeps the property's modifiers, with the accessor's accessibility where the accessor narrows it, so private set gives a private Set method. The getter keeps the property's documentation and attributes.
  • A read x.Name becomes x.GetName(). An assignment x.Name = v becomes x.SetName(v). x.Name += v becomes x.SetName(x.GetName() + v), and x.Name++ becomes x.SetName(x.GetName() + 1).

Preserved

  • Every value read and written, given the precondition.
  • Comments inside accessor bodies.

Limitations

  • ??= has no single-call equivalent and is refused.
  • Code outside the solution that uses the property is not updated.

Error codes

CodeMeaning
in-hierarchythe property is virtual, abstract, an override or part of an interface
init-accessorthe property has an init accessor
name-conflicta method or field name the conversion needs is taken
used-in-object-initializeran object initialiser sets the property
assignment-used-as-valuethe value of an assignment to the property is used
increment-used-as-valuethe value of an increment or decrement of the property is used
unsupported-assignmenta compound assignment, such as ??=, has no call equivalent
receiver-evaluated-twicea compound write's receiver is not a plain name
used-in-nameofa use is inside nameof

Cases

·

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

#accessor-bodies

Accessor bodies become method bodies, block for block, and the setter keeps its own accessibility

success
Targetsymbol P:Shop.Customer.Name
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 private string _name = "";
66
7− public string Name
7+ public string GetName()
88 {
9− get { return _name; }
10− protected set
11− {
12− // Names are stored trimmed.
13− _name = value.Trim();
14− }
9+ return _name;
1510 }
1611
17− public void Rename(string name) => Name = name;
12+ protected void SetName(string value)
13+ {
14+ // Names are stored trimmed.
15+ _name = value.Trim();
16+ }
17+
18+ public void Rename(string name) => SetName(name);
1819 }
1920 }

#auto-property-across-files

An auto-property becomes a backing field with Get and Set methods; reads, assignments, compound assignments and increments in other files call them

success
Targetsymbol P:Shop.Order.Quantity
Checkout.csmodified
11 namespace Shop
22 {
33 public class Checkout
44 {
55 public int Adjust(Order order, int extra)
66 {
7− order.Quantity = 2;
8− order.Quantity += extra * 2;
9− order.Quantity++;
10− return order.Quantity;
7+ order.SetQuantity(2);
8+ order.SetQuantity(order.GetQuantity() + extra * 2);
9+ order.SetQuantity(order.GetQuantity() + 1);
10+ return order.GetQuantity();
1111 }
1212 }
1313 }
Order.csmodified
11 namespace Shop
22 {
33 public class Order
44 {
5− public int Quantity { get; set; } = 1;
5+ private int _quantity = 1;
66
7− public bool IsBulk() => Quantity > 10;
7+ public int GetQuantity() => _quantity;
8+
9+ public void SetQuantity(int value) => _quantity = value;
10+
11+ public bool IsBulk() => GetQuantity() > 10;
812 }
913 }

#expression-bodied-with-documentation

An expression-bodied property becomes a Get method that keeps the property's documentation

success
Targetsymbol P:Shop.Basket.Total
Basket.csmodified
11 namespace Shop
22 {
33 public class Basket
44 {
55 private decimal _price;
66
77 public void Add(decimal price) => _price += price;
88
99 /// <summary>The price with tax.</summary>
10− public decimal Total => _price * 1.2m;
10+ public decimal GetTotal() => _price * 1.2m;
1111
12− public string Describe() => "Total: " + this.Total;
12+ public string Describe() => "Total: " + this.GetTotal();
1313 }
1414 }

#get-only-auto-property

A get-only auto-property gets a readonly field, which the constructor assigns directly

success
Targetsymbol P:Shop.Product.Code
Product.csmodified
11 namespace Shop
22 {
33 public class Product
44 {
5+ private readonly string _code;
6+
57 public Product(string code)
68 {
7− Code = code;
9+ _code = code;
810 }
911
10− public string Code { get; }
12+ public string GetCode() => _code;
1113 }
1214
1315 public class Catalogue
1416 {
15− public string Find(Product product) => product.Code;
17+ public string Find(Product product) => product.GetCode();
1618 }
1719 }

#in-hierarchy-rejected

Refuses a virtual property, whose overrides would have to change too

refusal
Targetsymbol P:Shop.Product.Label
Refusesin-hierarchy; every file is left unchanged
Product.csinput
1 namespace Shop
2 {
3 public class Product
4 {
5 public virtual string Label => "product";
6 }
7
8 public class Book : Product
9 {
10 public override string Label => "book";
11 }
12 }

#increment-used-as-value-rejected

Refuses an increment whose value is used, which a pair of calls cannot express as one expression

refusal
Targetsymbol P:Shop.Counter.Value
Refusesincrement-used-as-value; every file is left unchanged
Counter.csinput
1 namespace Shop
2 {
3 public class Counter
4 {
5 public int Value { get; set; }
6
7 public int Next() => Value++;
8 }
9 }

#init-accessor-rejected

Refuses a property with an init accessor, whose object-initialiser uses a method cannot replace

refusal
Targetsymbol P:Shop.Sample.Name
Refusesinit-accessor; every file is left unchanged
Sample.csinput
1 namespace Shop
2 {
3 public class Sample
4 {
5 public string Name { get; init; } = "";
6 }
7 }

#name-conflict-rejected

Refuses when a method name the conversion needs is taken

refusal
Targetsymbol P:Shop.Sample.Count
Refusesname-conflict mentioning “'GetCount'”; every file is left unchanged
Sample.csinput
1 namespace Shop
2 {
3 public class Sample
4 {
5 public int Count { get; set; }
6
7 public int GetCount(int extra) => Count + extra;
8 }
9 }

#receiver-evaluated-twice-rejected

Refuses a compound assignment through a receiver that is a call, which would run twice

refusal
Targetsymbol P:Shop.Counter.Value
Refusesreceiver-evaluated-twice; every file is left unchanged
Counter.csinput
1 namespace Shop
2 {
3 public class Counter
4 {
5 public int Value { get; set; }
6 }
7
8 public class Tally
9 {
10 private readonly Counter _counter = new Counter();
11
12 public Counter Current() => _counter;
13
14 public void Bump() => Current().Value += 1;
15 }
16 }

#static-nullable

A static nullable property gives static methods with the same nullable type

success
Targetsymbol P:Shop.Registry.Last
Projectnullable enable
Registry.csmodified
11 namespace Shop
22 {
33 public static class Registry
44 {
5− public static string? Last { get; private set; }
5+ private static string? _last;
66
7− public static void Register(string name) => Last = name;
7+ public static string? GetLast() => _last;
88
9− public static int Length() => Last?.Length ?? 0;
9+ private static void SetLast(string? value) => _last = value;
10+
11+ public static void Register(string name) => SetLast(name);
12+
13+ public static int Length() => GetLast()?.Length ?? 0;
1014 }
1115 }

#used-in-object-initializer-rejected

Refuses a property set in an object initialiser, which cannot call a method

refusal
Targetsymbol P:Shop.Order.Quantity
Refusesused-in-object-initializer; every file is left unchanged
Order.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 public int Quantity { get; set; }
6 }
7
8 public class Store
9 {
10 public Order Single() => new Order { Quantity = 1 };
11 }
12 }