Adds to a class a member that forwards to the member of the same name of one of its fields, or of its base class. Replace Inheritance with Delegation uses it to give a class, while it still derives from its base class, its own members for the inherited ones other code uses, so the class can later stop deriving without its callers noticing.
Arguments
Argument Required Meaning memberyes The member to forward to: its name, this for an indexer, or its documentation comment id (M:Shop.Printer.Print(System.String)) when overloads share the name viayes The name of the field to forward through, or base for the base class
The target is the class, by symbol: "target": { "symbol": "T:Shop.Stack" }.
Precondition
The target is a class or struct; with base, a class deriving from a class other than object. With a field name, the class declares that field. The field's type, or the base class and the classes it derives from, has an instance member of that name the class can reach through the field or base, and exactly one unless it is named by id. The member is a method, property, indexer or field. Events, static members, generic methods, methods with ref, out, in, params or optional parameters, and members returning by reference are refused. The class does not already declare a member with that name, other than a method overload with different parameter types. No existing reference changes meaning, other than in the ways below, and the result compiles.
The new member has the forwarded member's name, type, parameters and accessibility. A method becomes an expression-bodied method (public string Greeting(string salutation) => _customer.Greeting(salutation);). A property or indexer becomes a property or indexer whose getter reads the member and, when the forwarded member has a setter as accessible as itself, whose setter writes it, with the accessors on one line. A field becomes a property, get-only when the field is readonly. It is placed after the class's fields and the members right after them that already forward through the same field or base, or first in the class when there are none, with a blank line on each side. When it hides an inherited member, as a member forwarding to base always does, it is declared new. Inside the class, uses of an inherited member the new one hides, written bare or with this, are written with base (this[Count - 1] becomes this[base.Count - 1] when forwarding Count), so they keep reaching the inherited member. Elsewhere, uses through the class of the member forwarded to base now reach the new member, which reaches the same member.
Preserved
What every existing reference reaches when it runs. Comments and blank lines around the neighbouring members.
Limitations
Implicit uses, such as a collection initializer calling an Add the new member hides, are not checked. The accessors of a forwarding property or indexer are written on one line.
Error codes
Code Meaning not-a-classthe target is not a class or struct field-not-foundthe class has no field with the name via gives no-base-classvia is base and the class derives only from objectmember-not-foundthe field's type or the base class has no such member the class can reach ambiguous-memberseveral members share the name; name one by its documentation comment id unsupported-memberthe member is an event, static, generic, has ref, out, in, params or optional parameters, or returns by reference name-conflictthe class already declares a member of that name changes-meaninga reference outside the class, or to a member the class declares, would reach the new member instead breaks-compilationthe result would not compile, for example because the new member would expose a less accessible type
Cases
Expand all · Collapse all
Diffs show each file from before to after .
/*[*/ … /*]*/ marks a selection and /*^*/ a caret; the runner removes them before the refactoring runs.
# after-existing-delegating-membersThe new member goes after the members that already forward to the same field, and comments on the neighbouring members stay with them
success
Target symbol T:Shop.Order
Arguments member"Greeting"via"_customer"
Order.cs modified
1 1 namespace Shop 2 2 { 3 3 public class Order 4 4 { 5 5 // Who placed the order. 6 6 private readonly Customer _customer = new Customer (); 7 7 8 8 // Forwarded so callers need not reach the customer. 9 9 public string Name => _customer.Name ; 10 10 11 + public string Greeting (string salutation) => _customer.Greeting (salutation); 12 + 11 13 /// <summary>The order's total.</summary> 12 14 public decimal Total { get ; set ; } 13 15 } 14 16 }
Customer.cs unchanged
1 1 namespace Shop 2 2 { 3 3 public class Customer 4 4 { 5 5 public string Name { get ; set ; } = "" ; 6 6 7 7 public string Greeting (string salutation) => salutation + " " + Name ; 8 8 } 9 9 }
View fixture on GitHub
# after-forwarding-to-baseA second member forwarding to the base class goes after the first, and callers elsewhere that used the inherited indexer reach the new one
success
Target symbol T:Shop.Stack
Stack.cs modified
1 1 using System .Collections .Generic ;2 2 3 3 namespace Shop 4 4 { 5 5 // Last in, first out. 6 6 public class Stack : List <int > 7 7 { 8 8 private readonly List <int > _list = new List <int >(); 9 9 10 10 public new int Count => base .Count ; 11 11 12 + public new int this [int index] { get => base [index]; set => base [index] = value ; } 13 + 12 14 public void Push (int value ) => Add (value ); 13 15 14 16 public int Pop () 15 17 { 16 − var top = this [base .Count - 1 ]; 18 + var top = base [base .Count - 1 ]; 17 19 RemoveAt (base .Count - 1 ); 18 20 return top; 19 21 } 20 22 } 21 23 }
Client.cs unchanged
1 1 namespace Shop 2 2 { 3 3 public class Client 4 4 { 5 5 public int Run () 6 6 { 7 7 var stack = new Stack (); 8 8 stack.Push (1 ); 9 9 stack[0 ] = 5 ; 10 10 return stack.Pop () + stack.Count + stack[0 ]; 11 11 } 12 12 } 13 13 }
View fixture on GitHub
# ambiguous-member-rejectedRefuses a name several overloads share; the member must be named by its id
refusal
Target symbol T:Shop.Report
Arguments member"Print"via"_printer"
Refuses ambiguous-member; every file is left unchanged
Printer.cs input
1 namespace Shop 2 { 3 public class Printer 4 { 5 public void Print (string text) { } 6 7 public void Print (int number) { } 8 } 9 }
Report.cs input
1 namespace Shop 2 { 3 public class Report 4 { 5 private readonly Printer _printer = new Printer (); 6 } 7 }
View fixture on GitHub
# breaks-compilation-rejectedRefuses when the forwarding member would not compile: a public method of a public class cannot return an internal type
refusal
Target symbol T:Shop.Car
Arguments member"Spare"via"_engine"
Refuses breaks-compilation; every file is left unchanged
Car.cs input
1 namespace Shop 2 { 3 internal class Part 4 { 5 } 6 7 internal class Engine 8 { 9 public Part Spare () => new Part (); 10 } 11 12 public class Car 13 { 14 private readonly Engine _engine = new Engine (); 15 16 public bool HasEngine () => _engine != null ; 17 } 18 }
View fixture on GitHub
# changes-meaning-rejectedRefuses when the new member would hide an inherited member other code uses, which would then reach the field instead
refusal
Target symbol T:Shop.Order
Arguments member"Name"via"_customer"
Refuses changes-meaning mentioning “Order.cs(17,”; every file is left unchanged
Customer.cs input
1 namespace Shop 2 { 3 public class Customer 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string Greeting (string salutation) => salutation + " " + Name ; 8 } 9 }
Order.cs input
1 namespace Shop 2 { 3 public class Document 4 { 5 public string Name { get ; set ; } = "" ; 6 } 7 8 public class Order : Document 9 { 10 private readonly Customer _customer = new Customer (); 11 12 public string Label () => Name + " for " + _customer.Name ; 13 } 14 15 public class Archive 16 { 17 public string Title (Order order) => order.Name ; 18 } 19 }
View fixture on GitHub
# field-as-propertyA readonly field of the delegate is forwarded by a get-only property
success
Target symbol T:Shop.OrderLine
OrderLine.cs modified
1 1 namespace Shop 2 2 { 3 3 public class OrderLine 4 4 { 5 5 private readonly Product _product = new Product (); 6 6 private int _quantity = 1 ; 7 7 8 + public string Sku => _product.Sku ; 9 + 8 10 public decimal Total () => _product.Price * _quantity; 9 11 } 10 12 }
Product.cs unchanged
1 1 namespace Shop 2 2 { 3 3 public class Product 4 4 { 5 5 public readonly string Sku = "A-1" ; 6 6 public decimal Price ; 7 7 } 8 8 }
View fixture on GitHub
# field-not-found-rejectedRefuses when the class has no field of the given name
refusal
Target symbol T:Shop.Order
Refuses field-not-found mentioning “_buyer”; every file is left unchanged
Customer.cs input
1 namespace Shop 2 { 3 public class Customer 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string Greeting (string salutation) => salutation + " " + Name ; 8 } 9 }
Order.cs input
1 namespace Shop 2 { 3 public class Order 4 { 5 private readonly Customer _customer = new Customer (); 6 7 public decimal Total { get ; set ; } 8 } 9 }
View fixture on GitHub
# generic-field-typeForwarding to a field of a constructed generic type writes the member with the type arguments substituted
success
Target symbol T:Shop.Tally
Arguments member"ContainsKey"via"_counts"
Tally.cs modified
1 1 using System .Collections .Generic ;2 2 3 3 namespace Shop 4 4 { 5 5 public class Tally 6 6 { 7 7 private readonly Dictionary <string , int > _counts = new Dictionary <string , int >(); 8 8 9 + public bool ContainsKey (string key) => _counts.ContainsKey (key); 10 + 9 11 public void Count (string word) => _counts[word] = _counts.GetValueOrDefault (word) + 1 ; 10 12 } 11 13 }
View fixture on GitHub
# indexer-via-baseAn indexer is named this; the forwarding indexer keeps its setter, and the class's own element accesses use base
success
Target symbol T:Shop.Stack
Stack.cs modified
1 1 using System .Collections .Generic ;2 2 3 3 namespace Shop 4 4 { 5 5 public class Stack : List <int > 6 6 { 7 + public new int this [int index] { get => base [index]; set => base [index] = value ; } 8 + 7 9 public void Push (int value ) => Add (value ); 8 10 9 11 public int Pop () 10 12 { 11 − var top = this [Count - 1 ]; 13 + var top = base [Count - 1 ]; 12 14 RemoveAt (this .Count - 1 ); 13 15 return top; 14 16 } 15 17 } 16 18 }
View fixture on GitHub
# member-not-found-rejectedRefuses a member the field's type does not have
refusal
Target symbol T:Shop.Order
Arguments member"Surname"via"_customer"
Refuses member-not-found mentioning “Surname”; every file is left unchanged
Customer.cs input
1 namespace Shop 2 { 3 public class Customer 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string Greeting (string salutation) => salutation + " " + Name ; 8 } 9 }
Order.cs input
1 namespace Shop 2 { 3 public class Order 4 { 5 private readonly Customer _customer = new Customer (); 6 7 public decimal Total { get ; set ; } 8 } 9 }
View fixture on GitHub
# member-of-grandbaseForwarding to the base reaches a member the base class inherited itself
success
Target symbol T:Staff.Manager
People.cs modified
1 1 namespace Staff 2 2 { 3 3 public class Person 4 4 { 5 5 public string Name { get ; set ; } = "" ; 6 6 } 7 7 8 8 public class Employee : Person 9 9 { 10 10 public decimal Salary { get ; set ; } 11 11 } 12 12 13 13 public class Manager : Employee 14 14 { 15 − public string Title () => "Manager " + Name ; 15 + public new string Name { get => base .Name ; set => base .Name = value ; } 16 + 17 + public string Title () => "Manager " + base .Name ; 16 18 } 17 19 }
View fixture on GitHub
# name-conflict-rejectedRefuses when the class already declares a member of that name
refusal
Target symbol T:Shop.Order
Arguments member"Greeting"via"_customer"
Refuses name-conflict mentioning “Greeting”; every file is left unchanged
Customer.cs input
1 namespace Shop 2 { 3 public class Customer 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string Greeting (string salutation) => salutation + " " + Name ; 8 } 9 }
Order.cs input
1 namespace Shop 2 { 3 public class Order 4 { 5 private readonly Customer _customer = new Customer (); 6 7 public string Greeting (string salutation) => salutation + ", your order" ; 8 } 9 }
View fixture on GitHub
# no-base-class-rejectedRefuses to forward to the base class of a class that derives only from object
refusal
Target symbol T:Shop.Order
Refuses no-base-class; every file is left unchanged
Customer.cs input
1 namespace Shop 2 { 3 public class Customer 4 { 5 public string Name { get ; set ; } = "" ; 6 7 public string Greeting (string salutation) => salutation + " " + Name ; 8 } 9 }
Order.cs input
1 namespace Shop 2 { 3 public class Order 4 { 5 private readonly Customer _customer = new Customer (); 6 7 public decimal Total { get ; set ; } 8 } 9 }
View fixture on GitHub
# not-a-class-rejectedRefuses an interface, which cannot hold a field or forward to a base class
refusal
Target symbol T:Shop.IOrder
Refuses not-a-class; every file is left unchanged
IOrder.cs input
1 namespace Shop 2 { 3 public interface IOrder 4 { 5 decimal Total { get ; } 6 } 7 }
View fixture on GitHub
# nullableNullable annotations on the forwarded member's type and parameters are kept
success
Target symbol T:Shop.Order
Order.cs modified
1 1 #nullable enable 2 2 3 3 namespace Shop 4 4 { 5 5 public class Order 6 6 { 7 7 private readonly AddressBook _book = new AddressBook (); 8 + 9 + public string ? Find (string ? prefix) => _book.Find (prefix); 8 10 } 9 11 }
AddressBook.cs unchanged
1 1 #nullable enable 2 2 3 3 namespace Shop 4 4 { 5 5 public class AddressBook 6 6 { 7 7 public string ? Find (string ? prefix) => prefix; 8 8 } 9 9 }
View fixture on GitHub
# overload-by-idAn overloaded member is named by its documentation comment id, and only that overload is forwarded
success
Target symbol T:Shop.Report
Arguments member"M:Shop.Printer.Print(System.String)"via"_printer"
Report.cs modified
1 1 namespace Shop 2 2 { 3 3 public class Report 4 4 { 5 5 private readonly Printer _printer = new Printer (); 6 + 7 + public void Print (string text) => _printer.Print (text); 6 8 } 7 9 }
Printer.cs unchanged
1 1 namespace Shop 2 2 { 3 3 public class Printer 4 4 { 5 5 public void Print (string text) { } 6 6 7 7 public void Print (int number) { } 8 8 } 9 9 }
View fixture on GitHub
# property-with-setterA property with a setter the class can reach is forwarded with both accessors
success
Target symbol T:Shop.Order
Arguments member"Name"via"_customer"
Order.cs modified
1 1 namespace Shop 2 2 { 3 3 public class Order 4 4 { 5 5 private readonly Customer _customer = new Customer (); 6 6 7 + public string Name { get => _customer.Name ; set => _customer.Name = value ; } 8 + 7 9 public decimal Total { get ; set ; } 8 10 } 9 11 }
Customer.cs unchanged
1 1 namespace Shop 2 2 { 3 3 public class Customer 4 4 { 5 5 public string Name { get ; set ; } = "" ; 6 6 7 7 public string Greeting (string salutation) => salutation + " " + Name ; 8 8 } 9 9 }
View fixture on GitHub
# unsupported-member-rejectedRefuses an event, which a forwarding member cannot reproduce
refusal
Target symbol T:Shop.Order
Arguments member"Changed"via"_customer"
Refuses unsupported-member; every file is left unchanged
Customer.cs input
1 using System ;2 3 namespace Shop 4 { 5 public class Customer 6 { 7 public event EventHandler Changed ; 8 9 public void Touch () => Changed ?.Invoke (this , EventArgs .Empty ); 10 } 11 }
Order.cs input
1 namespace Shop 2 { 3 public class Order 4 { 5 private readonly Customer _customer = new Customer (); 6 7 public decimal Total { get ; set ; } 8 } 9 }
View fixture on GitHub
# via-baseA member forwarding to the base class hides the inherited one; the class's own uses of the inherited member say base so they keep reaching it, and callers elsewhere reach the new member
success
Target symbol T:Shop.Stack
Stack.cs modified
1 1 using System .Collections .Generic ;2 2 3 3 namespace Shop 4 4 { 5 5 public class Stack : List <int > 6 6 { 7 + public new int Count => base .Count ; 8 + 7 9 public void Push (int value ) => Add (value ); 8 10 9 11 public int Pop () 10 12 { 11 − var top = this [Count - 1 ]; 12 − RemoveAt (this .Count - 1 ); 13 + var top = this [base .Count - 1 ]; 14 + RemoveAt (base .Count - 1 ); 13 15 return top; 14 16 } 15 17 } 16 18 }
Client.cs unchanged
1 1 namespace Shop 2 2 { 3 3 public class Client 4 4 { 5 5 public int Run () 6 6 { 7 7 var stack = new Stack (); 8 8 stack.Push (1 ); 9 9 return stack.Pop () + stack.Count ; 10 10 } 11 11 } 12 12 }
View fixture on GitHub
# via-fieldAdds a method that forwards to the same method of a field, after the fields
success
Target symbol T:Shop.Order
Arguments member"Greeting"via"_customer"
Order.cs modified
1 1 namespace Shop 2 2 { 3 3 public class Order 4 4 { 5 5 private readonly Customer _customer = new Customer (); 6 6 7 + public string Greeting (string salutation) => _customer.Greeting (salutation); 8 + 7 9 public decimal Total { get ; set ; } 8 10 } 9 11 }
Customer.cs unchanged
1 1 namespace Shop 2 2 { 3 3 public class Customer 4 4 { 5 5 public string Name { get ; set ; } = "" ; 6 6 7 7 public string Greeting (string salutation) => salutation + " " + Name ; 8 8 } 9 9 }
View fixture on GitHub