RefactorMCP catalog

Convert Method to Property

Turns a parameterless method that returns a value, such as GetTotal(), into a get-only property, and every call into a property read.

Precondition

  • The target is a parameterless method that returns a value and is not generic or async.
  • It should be free of side effects and cheap, as a property read is expected to be. The refactoring cannot prove this; choosing the method is the caller's judgement.
  • It is not an override; convert the method it overrides, which converts every override with it.
  • It does not implement an interface member.
  • Every use calls it. A method group, such as a delegate conversion or nameof, is refused.
  • The property name is free in the declaring type and in every type that overrides the method.

Transformation

  • The property is named by the name argument, or by the method name without a leading Get: GetTotal becomes Total, Describe stays Describe.
  • An expression body, or a block that only returns a value and has no comments, becomes an expression-bodied property. Any other block becomes the get accessor. An abstract method becomes { get; }.
  • Modifiers, attributes and documentation are kept, so a virtual method gives a virtual property. Every override becomes an overriding property.
  • Every call, in any file, becomes a read: order.GetTotal() becomes order.Total, order?.GetTotal() becomes order?.Total and base.GetLabel() becomes base.Label.
  • Only the parameterless overload is converted; other overloads and their calls are left alone.

Preserved

  • The value every call site receives, given the precondition.
  • Comments inside a block body.

Limitations

  • Code outside the solution that calls the method is not updated.

Error codes

CodeMeaning
has-parametersthe method, and every overload of its name, takes parameters
returns-voidthe method returns nothing
generic-methodthe method has type parameters
async-methodthe method is async
is-overridethe method overrides another
implements-interfacethe method implements an interface member
used-as-method-groupa use of the method does not call it
name-conflictthe property name is taken in the type or an overriding type

Cases

·

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

#abstract-method

An abstract method becomes an abstract get-only property, and its implementations become properties

success
Targetsymbol M:Shop.Product.GetKind
Product.csmodified
11 namespace Shop
22 {
33 public abstract class Product
44 {
5− public abstract string GetKind();
5+ public abstract string Kind { get; }
66 }
77
88 public class Book : Product
99 {
10− public override string GetKind() => "book";
10+ public override string Kind => "book";
1111 }
1212 }

#generic-method-rejected

Refuses a generic method, since a property cannot have type parameters

refusal
Targetsymbol M:Shop.Sample.GetEmpty``1
Refusesgeneric-method; every file is left unchanged
Sample.csinput
1 namespace Shop
2 {
3 public class Sample
4 {
5 public T[] GetEmpty<T>() => new T[0];
6 }
7 }

#getter-across-files

A parameterless Get method becomes a property without the prefix, and calls in other files read the property

success
Targetsymbol M:Shop.Order.GetTotal
Invoice.csmodified
11 namespace Shop
22 {
33 public class Invoice
44 {
5− public string Line(Order order) => "Total: " + order.GetTotal();
5+ public string Line(Order order) => "Total: " + order.Total;
66
7− public decimal Tax(Order order) => (order?.GetTotal() ?? 0) * 0.2m;
7+ public decimal Tax(Order order) => (order?.Total ?? 0) * 0.2m;
88 }
99 }
Order.csmodified
11 namespace Shop
22 {
33 public class Order
44 {
55 private readonly decimal _price;
66 private readonly int _quantity;
77
88 public Order(decimal price, int quantity)
99 {
1010 _price = price;
1111 _quantity = quantity;
1212 }
1313
14− public decimal GetTotal()
15− {
16− return _price * _quantity;
17− }
14+ public decimal Total => _price * _quantity;
1815
19− public bool IsLarge() => GetTotal() > 100;
16+ public bool IsLarge() => Total > 100;
2017 }
2118 }

#has-parameters-rejected

Refuses a method that takes parameters

refusal
Targetsymbol M:Shop.Sample.GetScaled(System.Int32)
Refuseshas-parameters; every file is left unchanged
Sample.csinput
1 namespace Shop
2 {
3 public class Sample
4 {
5 public int GetScaled(int factor) => 10 * factor;
6 }
7 }

#implements-interface-rejected

Refuses a method that implements an interface member, which would no longer be implemented

refusal
Targetsymbol M:Shop.Book.GetTitle
Refusesimplements-interface; every file is left unchanged
Book.csinput
1 namespace Shop
2 {
3 public interface ITitled
4 {
5 string GetTitle();
6 }
7
8 public class Book : ITitled
9 {
10 public string GetTitle() => "Emma";
11 }
12 }

#name-conflict-rejected

Refuses a property name another member already has

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

#nullable-return

A nullable return type becomes the property's type

success
Targetsymbol M:Shop.Profile.GetNickname
Projectnullable enable
Profile.csmodified
11 namespace Shop
22 {
33 public class Profile
44 {
55 private string? _nickname;
66
7− public string? GetNickname() => _nickname;
7+ public string? Nickname => _nickname;
88
99 public void Forget() => _nickname = null;
1010
11− public int Length() => GetNickname()?.Length ?? 0;
11+ public int Length() => Nickname?.Length ?? 0;
1212 }
1313 }

#overloads

Only the parameterless overload becomes a property; calls to the other overload are left alone

success
Targetsymbol M:Shop.Item.GetPrice
Item.csmodified
11 namespace Shop
22 {
33 public class Item
44 {
5− public decimal GetPrice() => 10m;
5+ public decimal Price => 10m;
66
7− public decimal GetPrice(int quantity) => GetPrice() * quantity;
7+ public decimal GetPrice(int quantity) => Price * quantity;
88 }
99
1010 public class Till
1111 {
12− public decimal Charge(Item item) => item.GetPrice() + item.GetPrice(2);
12+ public decimal Charge(Item item) => item.Price + item.GetPrice(2);
1313 }
1414 }

#override-rejected

Refuses an override, which only its base declaration can convert

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

#returns-void-rejected

Refuses a method that returns nothing

refusal
Targetsymbol M:Shop.Sample.Reset
Refusesreturns-void; every file is left unchanged
Sample.csinput
1 namespace Shop
2 {
3 public class Sample
4 {
5 private int _count;
6
7 public void Reset() => _count = 0;
8
9 public int Count() => _count;
10 }
11 }

#several-statements

A method body of several statements becomes the property's get accessor

success
Targetsymbol M:Shop.Customer.GetSummary
Customer.csmodified
11 namespace Shop
22 {
33 public class Customer
44 {
55 private string _name = "";
66
7− public string GetSummary()
7+ public string Summary
88 {
9− // Names are stored untrimmed.
10− var text = _name.Trim();
11− return text.ToUpperInvariant();
9+ get
10+ {
11+ // Names are stored untrimmed.
12+ var text = _name.Trim();
13+ return text.ToUpperInvariant();
14+ }
1215 }
1316
1417 public void Rename(string name) => _name = name;
1518 }
1619 }

#static-with-name-and-documentation

A static method becomes a static property with the given name, keeping its documentation

success
Targetsymbol M:Shop.Settings.CreateDefault
Arguments
name"Default"
Settings.csmodified
11 namespace Shop
22 {
33 public class Settings
44 {
55 public int Retries;
66
77 /// <summary>Settings with every value at its default.</summary>
8− public static Settings CreateDefault() => new Settings { Retries = 3 };
8+ public static Settings Default => new Settings { Retries = 3 };
99 }
1010
1111 public class Client
1212 {
13− public int Retries() => Settings.CreateDefault().Retries;
13+ public int Retries() => Settings.Default.Retries;
1414 }
1515 }

#used-as-method-group-rejected

Refuses a method used without being called, such as a delegate

refusal
Targetsymbol M:Shop.Counter.GetCount
Refusesused-as-method-group; every file is left unchanged
Counter.csinput
1 using System;
2
3 namespace Shop
4 {
5 public class Counter
6 {
7 public int GetCount() => 3;
8
9 public Func<int> Reader() => GetCount;
10 }
11 }

#virtual-with-overrides

A virtual method becomes a virtual property, and every override becomes an overriding property

success
Targetsymbol M:Shop.Product.GetLabel
Book.csmodified
11 namespace Shop
22 {
33 public class Book : Product
44 {
55 public override string GetKind() => "book";
66
7− public override string GetLabel() => "book " + base.GetLabel();
7+ public override string Label => "book " + base.Label;
88 }
99 }
Product.csmodified
11 namespace Shop
22 {
33 public abstract class Product
44 {
55 public abstract string GetKind();
66
7− public virtual string GetLabel() => "product";
7+ public virtual string Label => "product";
88
9− public string Describe() => GetKind() + ": " + GetLabel();
9+ public string Describe() => GetKind() + ": " + Label;
1010 }
1111 }