RefactorMCP catalog

Encapsulate Field

Makes a field private behind a property that reads and writes it, and points code outside the declaring type at the property.

Precondition

  • The target is a field that is not const, declared on its own rather than alongside other fields.
  • Code outside the declaring type does not pass the field by ref, out or in, which a property cannot be.
  • The property name, and the field's new name when it needs one, are free in the type.

Transformation

  • The property is named by the name argument, or from the field: _title and m_title give Title, title gives Title.
  • When the field already has the property's name, as a public field Quantity does, the field is renamed to _quantity.
  • The field becomes private, keeping static, readonly and its other modifiers and its initialiser.
  • The property has the field's accessibility, or is public when the field was private, is static when the field is, and is placed after the type's fields. It reads and writes the field through expression-bodied accessors, one to a line; a readonly field gets a get-only expression-bodied property.
  • References outside the declaring type, including subclasses and other files, use the property. References inside it, including nested types and other parts of a partial type, keep using the field.
  • The field's documentation comment moves to the property; ordinary comments stay with the field.

Preserved

  • Every read and write, including compound assignments and increments, which work the same through the property.
  • Nullable annotations on the field's type, which the property carries.

Limitations

  • Code outside the solution that used a public field is not updated, and becomes a binary-breaking change for compiled consumers.
  • Uses inside the type are not redirected to the property; a later refactoring can do that when the property gains behaviour.

Error codes

CodeMeaning
constant-fieldthe field is const
multiple-declaratorsthe field is declared alongside others
name-conflictthe property name, or the field's new name, is taken
passed-by-referencecode outside the type passes the field by reference

Cases

·

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

#constant-rejected

Refuses a constant, which has no storage to encapsulate

refusal
Targetsymbol F:Shop.Sample.Limit
Refusesconstant-field; every file is left unchanged
Sample.csinput
1 namespace Shop
2 {
3 public class Sample
4 {
5 public const int Limit = 10;
6 }
7 }

#doc-comment-moves-to-property

The field's documentation comment moves to the property, which is what callers now see; an ordinary comment stays with the field

success
Targetsymbol F:Shop.Account.Balance
Account.csmodified
11 namespace Shop
22 {
33 public class Account
44 {
55 // Kept in pence.
6− /// <summary>The current balance.</summary>
7− public long Balance;
6+ private long _balance;
87
98 public string Owner = "";
9+
10+ /// <summary>The current balance.</summary>
11+ public long Balance
12+ {
13+ get => _balance;
14+ set => _balance = value;
15+ }
1016 }
1117 }

#multiple-declarators-rejected

Refuses a field declared alongside others

refusal
Targetsymbol F:Shop.Point.X
Refusesmultiple-declarators; every file is left unchanged
Point.csinput
1 namespace Shop
2 {
3 public class Point
4 {
5 public int X, Y;
6 }
7 }

#name-conflict-rejected

Refuses a property name another member already has

refusal
Targetsymbol F:Shop.Sample._count
Arguments
name"Total"
Refusesname-conflict mentioning “'Total'”; every file is left unchanged
Sample.csinput
1 namespace Shop
2 {
3 public class Sample
4 {
5 private int _count;
6
7 public int Total() => _count;
8 }
9 }

#passed-by-reference-rejected

Refuses when code outside the type passes the field by reference, which a property cannot be

refusal
Targetsymbol F:Shop.Counter.Hits
Refusespassed-by-reference; every file is left unchanged
Counter.csinput
1 using System.Threading;
2
3 namespace Shop
4 {
5 public class Counter
6 {
7 public int Hits;
8 }
9
10 public class Tracker
11 {
12 public void Record(Counter counter) => Interlocked.Increment(ref counter.Hits);
13 }
14 }

#protected-field-in-subclass

A protected field used by a subclass gets a protected property with the given name, and the subclass uses the property

success
Targetsymbol F:Shop.Product._title
Arguments
name"DisplayTitle"
Book.csmodified
11 namespace Shop
22 {
33 public class Book : Product
44 {
55 public void Rename(string title)
66 {
7− _title = title;
7+ DisplayTitle = title;
88 }
99
10− public string Spine() => _title + " (paperback)";
10+ public string Spine() => DisplayTitle + " (paperback)";
1111 }
1212 }
Product.csmodified
11 namespace Shop
22 {
33 public class Product
44 {
5− protected string _title = "";
5+ private string _title = "";
66 private decimal _price;
7+
8+ protected string DisplayTitle
9+ {
10+ get => _title;
11+ set => _title = value;
12+ }
713
814 public decimal Price() => _price;
915
1016 public string Label() => _title.ToUpperInvariant();
1117 }
1218 }

#public-field-across-files

A public field becomes a private field behind a public property of the same name; code in other types uses the property and the type itself keeps using the field

success
Targetsymbol F:Shop.Order.Quantity
Order.csmodified
11 namespace Shop
22 {
33 public class Order
44 {
5− public int Quantity;
5+ private int _quantity;
66
7− public int Weight() => Quantity * 10;
7+ public int Quantity
8+ {
9+ get => _quantity;
10+ set => _quantity = value;
11+ }
12+
13+ public int Weight() => _quantity * 10;
814 }
915 }
Checkout.csunchanged
11 namespace Shop
22 {
33 public class Checkout
44 {
55 public int Add(Order order)
66 {
77 order.Quantity = 2;
88 order.Quantity += 1;
99 return order.Quantity;
1010 }
1111 }
1212 }

#readonly-field

A readonly field gets a get-only property

success
Targetsymbol F:Shop.Product.Code
Product.csmodified
11 namespace Shop
22 {
33 public class Product
44 {
5− public readonly string Code;
5+ private readonly string _code;
6+
7+ public string Code => _code;
68
79 public Product(string code)
810 {
9− Code = code;
11+ _code = code;
1012 }
1113 }
1214 }
Catalogue.csunchanged
11 namespace Shop
22 {
33 public class Catalogue
44 {
55 public string Find(Product product) => product.Code;
66 }
77 }

#static-nullable-field

A static field with a nullable type gets a static property of the same type

success
Targetsymbol F:Shop.Registry.LastName
Projectnullable enable
Registry.csmodified
11 namespace Shop
22 {
33 public static class Registry
44 {
5− public static string? LastName;
5+ private static string? _lastName;
6+
7+ public static string? LastName
8+ {
9+ get => _lastName;
10+ set => _lastName = value;
11+ }
612 }
713
814 public class Customer
915 {
1016 public void Register(string name) => Registry.LastName = name;
1117 }
1218 }