Adds, removes and reorders the parameters of a method or constructor, and updates every call in the solution to match. Overrides, overridden methods, interface members and their implementations change together, since they must keep the same parameters.
Many composites build on this primitive: Introduce Parameter Object, Preserve Whole Object, Parameterise Method and Constructor Injection all change a signature as one of their steps.
Arguments
parameters is the complete new parameter list, in order. An existing parameter is named; a new one also gives its type, and either the value every existing call passes for it or a default, or both. A parameter of the old signature that is not listed is removed.
"arguments" : {
"parameters" : [
{ "name" : "b" },
{ "name" : "a" },
{ "name" : "scale" , "type" : "int" , "value" : "1" },
{ "name" : "label" , "type" : "string?" , "default" : "null" }
]
}
Field Meaning namean existing parameter's name, or the new parameter's name typethe type of a new parameter; given only for new parameters valuea C# expression each existing call passes for a new parameter defaulta default value declared for a new parameter; with no value, existing calls leave it out
replacements is optional. For a removed parameter the body still uses, it gives the expression that takes the place of each use, usually one reading the parameter's value from a new parameter:
"arguments" : {
"parameters" : [ { "name" : "point" , "type" : "Point" , "value" : "new Point(3, 4)" } ],
"replacements" : { "x" : "point.X" , "y" : "point.Y" }
}
The target is the method or constructor, by symbol. Targeting an override or an implementation changes the whole family.
Precondition
Every listed name is either an existing parameter or comes with a type. A new parameter has a value for existing calls, a default, or both. No removed parameter is read or written in the body of any member of the family, unless replacements gives an expression for it. A replaced parameter is only read, never assigned, and only removed parameters are replaced. A params array stays last, and no required parameter follows an optional one. The this parameter of an extension method stays first. Every member of the family is declared in the solution; a method that implements or overrides a member from a referenced assembly cannot change. The method is only called, never converted to a delegate: a method group would no longer match its delegate type. The result compiles.
Each declaration in the family gets the new parameter list. New parameters are declared with the given type and default. Each use of a replaced parameter, in every body of the family, becomes its expression, in parentheses unless it is a name or member access. Each call's arguments are rearranged into the new order, with the given value for each new parameter and nothing for a removed one. This covers invocations, base calls, object creation, target-typed new, and this() and base() constructor initializers. Named arguments keep their names. Once a call leaves an optional parameter to its default, every argument after it is named. The arguments a call passes to a params array in expanded form stay together, in order, at the end. A call to an extension method in reduced form keeps its receiver. References in nameof and documentation comments are left alone.
Preserved
Every call passes the same values to the same parameters, so the method sees what it saw before, apart from new parameters, which receive the given value. Layout: each position in a parameter or argument list keeps its line breaks and indentation, so a list laid out one per line stays that way. Comments travel with the parameter or argument they belong to, including a line comment after its comma. Nullable annotations on new and existing parameters.
Limitations
Arguments are evaluated in the new parameter order, so reordering calls whose arguments have side effects that depend on each other changes the order those side effects happen in. A removed parameter's argument is dropped even if evaluating it had side effects. Remove Unused Parameter refuses in that case. A replacement is taken on trust: behaviour is preserved only when, at every call, the expression gives the value the call passed for the parameter. <param> elements in documentation comments are not reordered, added or removed, and cref references with parameter lists are not updated.Delegates, indexers, operators, attribute constructors and primary constructors are not covered. A call that passes several values to a params array after an argument it leaves out cannot be named, and is refused.
Error codes
Code Meaning removed-parameter-in-usea parameter left out of the new list is used in a body replaced-parameter-assigneda parameter given a replacement is assigned in a body replaced-parameter-kepta parameter given a replacement is still in the new list unknown-parametera listed name is not a parameter and has no type to add it duplicate-parametera new parameter has the name of an existing one missing-valuea new parameter has neither a value for calls nor a default optional-before-requireda required parameter would follow an optional one params-not-lasta params array would not be last extension-this-movedthe this parameter of an extension method would move method-group-referencethe method is used as a method group external-memberthe method overrides or implements a member outside the solution
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.
# add-parameter-across-filesAdds a parameter and passes the given value at calls in another file
success
Target symbol M:Shop.Order.Total(System.Decimal)
Arguments parameters[{"name": "subtotal"}, {"name": "shipping", "type": "decimal", "value": "0m"}]
Checkout.cs modified
1 1 namespace Shop ;2 2 3 3 public class Checkout 4 4 { 5 5 public decimal Pay (Order order) 6 6 { 7 − return order.Total (100m ) + order.Total (20m ); 7 + return order.Total (100m , 0m ) + order.Total (20m , 0m ); 8 8 } 9 9 }
Order.cs modified
1 1 namespace Shop ;2 2 3 3 public class Order 4 4 { 5 − public decimal Total (decimal subtotal) 5 + public decimal Total (decimal subtotal, decimal shipping) 6 6 { 7 7 return subtotal; 8 8 } 9 9 }
View fixture on GitHub
# constructorAdds a parameter to a constructor, updating object creation, target-typed new, this() and base() calls
success
Target symbol M:Shop.Account.#ctor(System.String)
Arguments parameters[{"name": "owner"}, {"name": "limit", "type": "decimal", "value": "0m"}]
Account.cs modified
1 1 namespace Shop ;2 2 3 3 public class Account 4 4 { 5 5 private readonly string _owner; 6 6 7 − public Account (string owner) 7 + public Account (string owner, decimal limit) 8 8 { 9 9 _owner = owner; 10 10 } 11 11 12 − public Account () : this ("guest" ) 12 + public Account () : this ("guest" , 0m ) 13 13 { 14 14 } 15 15 16 16 public string Owner => _owner; 17 17 } 18 18 19 19 public class Savings : Account 20 20 { 21 − public Savings (string owner) : base (owner) 21 + public Savings (string owner) : base (owner, 0m ) 22 22 { 23 23 } 24 24 }
Bank.cs modified
1 1 namespace Shop ;2 2 3 3 public class Bank 4 4 { 5 5 public Account Open (string name) 6 6 { 7 − Account first = new Account (name); 8 − Account second = new ("joint" ); 7 + Account first = new Account (name, 0m ); 8 + Account second = new ("joint" , 0m ); 9 9 return first.Owner == name ? first : second; 10 10 } 11 11 }
View fixture on GitHub
# duplicate-parameter-rejectedRefuses to add a parameter with the name of one the method already has
refusal
Target symbol M:Shop.Pricing.Discount(System.Decimal,System.Int32)
Arguments parameters[{"name": "price"}, {"name": "percent"}, {"name": "price", "type": "decimal", "value": "0m"}]
Refuses duplicate-parameter mentioning “price”; every file is left unchanged
Pricing.cs input
1 namespace Shop ;2 3 public class Pricing 4 { 5 public decimal Discount (decimal price, int percent) 6 { 7 return price * percent / 100m ; 8 } 9 }
View fixture on GitHub
# extension-methodAdds a parameter to an extension method, called both as an extension and as a static method
success
Target symbol M:Shop.TextExtensions.Truncate(System.String,System.Int32)
Arguments parameters[{"name": "text"}, {"name": "length"}, {"name": "ellipsis", "type": "string", "value": "\"...\""}]
TextExtensions.cs modified
1 1 namespace Shop ;2 2 3 3 public static class TextExtensions 4 4 { 5 − public static string Truncate (this string text, int length) 5 + public static string Truncate (this string text, int length, string ellipsis) 6 6 { 7 7 return text.Length <= length ? text : text.Substring (0 , length); 8 8 } 9 9 } 10 10 11 11 public class Labels 12 12 { 13 13 public string Short (string name) 14 14 { 15 − return name.Truncate (10 ) + TextExtensions .Truncate (name, 3 ); 15 + return name.Truncate (10 , "..." ) + TextExtensions .Truncate (name, 3 , "..." ); 16 16 } 17 17 }
View fixture on GitHub
# extension-this-moved-rejectedRefuses to move the this parameter of an extension method away from the front
refusal
Target symbol M:Shop.TextExtensions.Truncate(System.String,System.Int32)
Arguments parameters[{"name": "length"}, {"name": "text"}]
Refuses extension-this-moved; every file is left unchanged
TextExtensions.cs input
1 namespace Shop ;2 3 public static class TextExtensions 4 { 5 public static string Truncate (this string text, int length) 6 { 7 return text.Length <= length ? text : text.Substring (0 , length); 8 } 9 }
View fixture on GitHub
# external-member-rejectedRefuses to change a method that implements an interface declared outside the solution
refusal
Target symbol M:Shop.Item.CompareTo(Shop.Item)
Arguments parameters[{"name": "other"}, {"name": "descending", "type": "bool", "value": "false"}]
Refuses external-member mentioning “IComparable”; every file is left unchanged
Item.cs input
1 using System ;2 3 namespace Shop ;4 5 public class Item : IComparable <Item >6 { 7 public int Rank { get ; set ; } 8 9 public int CompareTo (Item other) 10 { 11 return Rank .CompareTo (other.Rank ); 12 } 13 }
View fixture on GitHub
# generic-methodReorders a generic method's parameters at calls with inferred and explicit type arguments
success
Target symbol M:Shop.Choice.Pick``1(System.Boolean,``0,``0)
Arguments parameters[{"name": "first"}, {"name": "second"}, {"name": "preferFirst"}]
Choice.cs modified
1 1 namespace Shop ;2 2 3 3 public static class Choice 4 4 { 5 − public static T Pick <T >(bool preferFirst, T first, T second) 5 + public static T Pick <T >(T first, T second, bool preferFirst) 6 6 { 7 7 return preferFirst ? first : second; 8 8 } 9 9 } 10 10 11 11 public class Menu 12 12 { 13 13 public string Main () 14 14 { 15 − return Choice .Pick (true , "soup" , "salad" ) + Choice .Pick <int >(false , 1 , 2 ); 15 + return Choice .Pick ("soup" , "salad" , true ) + Choice .Pick <int >(1 , 2 , false ); 16 16 } 17 17 }
View fixture on GitHub
# interface-and-implementationsReorders an interface method's parameters in every implementation, explicit ones included, and at calls through the interface and through a class
success
Target symbol M:Shop.IShipping.Cost(System.Decimal,System.String)
Arguments parameters[{"name": "region"}, {"name": "weight"}]
Quote.cs modified
1 1 namespace Shop ;2 2 3 3 public class Quote 4 4 { 5 5 public decimal Price (IShipping shipping, Courier courier) 6 6 { 7 − return shipping.Cost (1.5m , "EU" ) + courier.Cost (2m , "US" ); 7 + return shipping.Cost ("EU" , 1.5m ) + courier.Cost ("US" , 2m ); 8 8 } 9 9 }
Shipping.cs modified
1 1 namespace Shop ;2 2 3 3 public interface IShipping 4 4 { 5 − decimal Cost (decimal weight, string region); 5 + decimal Cost (string region, decimal weight); 6 6 } 7 7 8 8 public class Courier : IShipping 9 9 { 10 − public decimal Cost (decimal weight, string region) 10 + public decimal Cost (string region, decimal weight) 11 11 { 12 12 return weight * 2m ; 13 13 } 14 14 } 15 15 16 16 public class Post : IShipping 17 17 { 18 − decimal IShipping .Cost (decimal weight, string region) 18 + decimal IShipping .Cost (string region, decimal weight) 19 19 { 20 20 return region == "EU" ? weight : weight * 3m ; 21 21 } 22 22 }
View fixture on GitHub
# method-group-rejectedRefuses when the method is converted to a delegate, which would no longer match
refusal
Target symbol M:Shop.Calculator.Add(System.Int32,System.Int32)
Arguments parameters[{"name": "b"}, {"name": "a"}]
Refuses method-group-reference mentioning “Add”; every file is left unchanged
Calculator.cs input
1 using System ;2 3 namespace Shop ;4 5 public class Calculator 6 { 7 public int Add (int a, int b) 8 { 9 return a + b; 10 } 11 12 public Func <int , int , int > Operation () 13 { 14 return Add ; 15 } 16 }
View fixture on GitHub
# missing-value-rejectedRefuses to add a parameter with neither a value for existing calls nor a default
refusal
Target symbol M:Shop.Pricing.Discount(System.Decimal,System.Int32)
Arguments parameters[{"name": "price"}, {"name": "percent"}, {"name": "rounding", "type": "int"}]
Refuses missing-value mentioning “rounding”; every file is left unchanged
Pricing.cs input
1 namespace Shop ;2 3 public class Pricing 4 { 5 public decimal Discount (decimal price, int percent) 6 { 7 return price * percent / 100m ; 8 } 9 }
View fixture on GitHub
# named-and-omitted-argumentsReorders and adds an optional parameter; named arguments keep their names and an argument after an omitted one becomes named
success
Target symbol M:Shop.Mailer.Compose(System.String,System.String,System.Boolean,System.String)
Arguments parameters[{"name": "subject"}, {"name": "to"}, {"name": "priority", "type": "int", "default": "0"}, {"name": "urgent"}, {"name": "footer"}]
Mailer.cs modified
1 1 namespace Shop ;2 2 3 3 public class Mailer 4 4 { 5 − public string Compose (string to, string subject, bool urgent = false , string footer = "" ) 5 + public string Compose (string subject, string to, int priority = 0 , bool urgent = false , string footer = "" ) 6 6 { 7 7 return to + subject + urgent + footer; 8 8 } 9 9 10 10 public void Send () 11 11 { 12 − Compose ("a@example.com" , "Hello" ); 12 + Compose ("Hello" , "a@example.com" ); 13 13 Compose (subject: "Hi" , to: "b@example.com" ); 14 − Compose ("c@example.com" , "Report" , footer: "Thanks" ); 15 − Compose ("d@example.com" , "Memo" , true ); 14 + Compose ("Report" , "c@example.com" , footer: "Thanks" ); 15 + Compose ("Memo" , "d@example.com" , urgent: true ); 16 16 } 17 17 }
View fixture on GitHub
# nullable-parameterAdds a nullable reference parameter in a nullable context, passing null
success
Target symbol M:Shop.Greeter.Greet(System.String)
Arguments parameters[{"name": "name"}, {"name": "title", "type": "string?", "value": "null"}]
Project nullable enable
Greeter.cs modified
1 1 namespace Shop ;2 2 3 3 public class Greeter 4 4 { 5 − public string Greet (string name) 5 + public string Greet (string name, string ? title) 6 6 { 7 7 return "Hello " + name; 8 8 } 9 9 10 10 public string Welcome (string ? nickname) 11 11 { 12 − return Greet (nickname ?? "friend" ); 12 + return Greet (nickname ?? "friend" , null ); 13 13 } 14 14 }
View fixture on GitHub
# optional-before-required-rejectedRefuses an order that puts an optional parameter before a required one
refusal
Target symbol M:Shop.Mailer.Compose(System.String,System.String)
Arguments parameters[{"name": "subject"}, {"name": "to"}]
Refuses optional-before-required mentioning “subject”; every file is left unchanged
Mailer.cs input
1 namespace Shop ;2 3 public class Mailer 4 { 5 public string Compose (string to, string subject = "" ) 6 { 7 return to + subject; 8 } 9 }
View fixture on GitHub
# overloadsChanges one overload and its calls, leaving the other overload and its calls alone
success
Target symbol M:Shop.Geometry.Area(System.Int32,System.Int32)
Arguments parameters[{"name": "height"}, {"name": "width"}]
Geometry.cs modified
1 1 namespace Shop ;2 2 3 3 public class Geometry 4 4 { 5 5 public int Area (int side) 6 6 { 7 7 return Area (side, side); 8 8 } 9 9 10 − public int Area (int width, int height) 10 + public int Area (int height, int width) 11 11 { 12 12 return width * height; 13 13 } 14 14 15 15 public int Total () 16 16 { 17 − return Area (3 ) + Area (4 , 5 ); 17 + return Area (3 ) + Area (5 , 4 ); 18 18 } 19 19 }
View fixture on GitHub
# override-hierarchyTargeting an override changes the virtual method it overrides too, and the base call inside it
success
Target symbol M:Shop.LoudNotifier.Format(System.String,System.Int32)
Arguments parameters[{"name": "message"}, {"name": "level"}, {"name": "channel", "type": "string", "value": "\"email\""}]
Notifier.cs modified
1 1 namespace Shop ;2 2 3 3 public class Notifier 4 4 { 5 − public virtual string Format (string message, int level) 5 + public virtual string Format (string message, int level, string channel) 6 6 { 7 7 return level + ": " + message; 8 8 } 9 9 } 10 10 11 11 public class LoudNotifier : Notifier 12 12 { 13 − public override string Format (string message, int level) 13 + public override string Format (string message, int level, string channel) 14 14 { 15 − return base .Format (message.ToUpperInvariant (), level) + "!" ; 15 + return base .Format (message.ToUpperInvariant (), level, "email" ) + "!" ; 16 16 } 17 17 } 18 18 19 19 public class Alerts 20 20 { 21 21 public string Raise (Notifier notifier) 22 22 { 23 − return notifier.Format ("disk full" , 2 ); 23 + return notifier.Format ("disk full" , 2 , "email" ); 24 24 } 25 25 }
View fixture on GitHub
# params-arrayAdds a parameter before a params array; expanded arguments stay together at the end
success
Target symbol M:Shop.Logger.Log(System.String,System.Object[])
Arguments parameters[{"name": "level", "type": "int", "value": "0"}, {"name": "format"}, {"name": "values"}]
Logger.cs modified
1 1 namespace Shop ;2 2 3 3 public class Logger 4 4 { 5 − public string Log (string format, params object [] values) 5 + public string Log (int level, string format, params object [] values) 6 6 { 7 7 return string .Format (format, values); 8 8 } 9 9 10 10 public string Run () 11 11 { 12 − return Log ("{0} {1}" , 1 , 2 ) + Log ("none" ); 12 + return Log (0 , "{0} {1}" , 1 , 2 ) + Log (0 , "none" ); 13 13 } 14 14 }
View fixture on GitHub
# params-not-last-rejectedRefuses an order that moves a params array away from the end
refusal
Target symbol M:Shop.Logger.Log(System.String,System.Object[])
Arguments parameters[{"name": "values"}, {"name": "format"}]
Refuses params-not-last mentioning “values”; every file is left unchanged
Logger.cs input
1 namespace Shop ;2 3 public class Logger 4 { 5 public string Log (string format, params object [] values) 6 { 7 return string .Format (format, values); 8 } 9 }
View fixture on GitHub
# preserves-triviaReorders parameters and arguments laid out one per line; comments travel with their parameter or argument and the layout stays
success
Target symbol M:Shop.Invoice.Line(System.String,System.Int32,System.Decimal)
Arguments parameters[{"name": "quantity"}, {"name": "product"}, {"name": "price"}]
Invoice.cs modified
1 1 namespace Shop ;2 2 3 3 public class Invoice 4 4 { 5 5 /// <summary>Builds a line.</summary> 6 6 public string Line ( 7 + int quantity, 7 8 string product, // what was sold 8 − int quantity, 9 9 decimal price) 10 10 { 11 11 return product + quantity + price; 12 12 } 13 13 14 14 public string Print () 15 15 { 16 16 // two line items 17 17 return Line ( 18 + 2 , 18 19 "pen" , // product 19 − 2 , 20 − 1.5m ) + Line ("cup" , /* count */ 1 , 2m ); 20 + 1.5m ) + Line (/* count */ 1 , "cup" , 2m ); 21 21 } 22 22 }
View fixture on GitHub
# remove-parameterRemoves a parameter the body does not read, and its argument
success
Target symbol M:Shop.Report.Title(System.String,System.Int32)
Arguments parameters[{"name": "name"}]
Report.cs modified
1 1 namespace Shop ;2 2 3 3 public class Report 4 4 { 5 − public string Title (string name, int width) 5 + public string Title (string name) 6 6 { 7 7 return name.ToUpperInvariant (); 8 8 } 9 9 10 10 public string Header () 11 11 { 12 − return Title ("sales" , 80 ); 12 + return Title ("sales" ); 13 13 } 14 14 }
View fixture on GitHub
# removed-parameter-in-use-rejectedRefuses to remove a parameter the body reads
refusal
Target symbol M:Shop.Pricing.Discount(System.Decimal,System.Int32)
Arguments parameters[{"name": "percent"}]
Refuses removed-parameter-in-use mentioning “price”; every file is left unchanged
Pricing.cs input
1 namespace Shop ;2 3 public class Pricing 4 { 5 public decimal Discount (decimal price, int percent) 6 { 7 return price * percent / 100m ; 8 } 9 }
View fixture on GitHub
# reorder-parametersSwaps two parameters and the arguments of a call in the same class
success
Target symbol M:Shop.Pricing.Discount(System.Decimal,System.Int32)
Arguments parameters[{"name": "percent"}, {"name": "price"}]
Pricing.cs modified
1 1 namespace Shop ;2 2 3 3 public class Pricing 4 4 { 5 − public decimal Discount (decimal price, int percent) 5 + public decimal Discount (int percent, decimal price) 6 6 { 7 7 return price * percent / 100m ; 8 8 } 9 9 10 10 public decimal Sale (decimal price) 11 11 { 12 − return price - Discount (price, 10 ); 12 + return price - Discount (10 , price); 13 13 } 14 14 }
View fixture on GitHub
# replace-removed-parameter-usesRemoves parameters the body uses by replacing each use with an expression over the new parameter
success
Target symbol M:Geometry.Grid.DistanceFromOrigin(System.Int32,System.Int32)
Arguments parameters[{"name": "point", "type": "Point", "value": "new Point(3, 4)"}]replacements{"x": "point.X", "y": "point.Y"}
Grid.cs modified
1 1 using System ;2 2 3 3 namespace Geometry 4 4 { 5 5 public record Point (int X , int Y ); 6 6 7 7 public class Grid 8 8 { 9 − public double DistanceFromOrigin (int x, int y) 9 + public double DistanceFromOrigin (Point point) 10 10 { 11 − return Math .Sqrt (x * x + y * y); 11 + return Math .Sqrt (point.X * point.X + point.Y * point.Y ); 12 12 } 13 13 14 14 public double Sample () 15 15 { 16 − return DistanceFromOrigin (3 , 4 ); 16 + return DistanceFromOrigin (new Point (3 , 4 )); 17 17 } 18 18 } 19 19 }
View fixture on GitHub
# replaced-parameter-assigned-rejectedRefuses to replace the uses of a parameter the body assigns, since an expression cannot be assigned in its place
refusal
Target symbol M:Geometry.Grid.Clamp(System.Int32,System.Int32)
Arguments parameters[{"name": "max"}, {"name": "limits", "type": "Limits", "value": "new Limits(0)"}]replacements{"value": "limits.Min"}
Refuses replaced-parameter-assigned mentioning “'value'”; every file is left unchanged
Grid.cs input
1 namespace Geometry 2 { 3 public record Limits (int Min ); 4 5 public class Grid 6 { 7 public int Clamp (int value , int max) 8 { 9 if (value > max) 10 value = max; 11 return value ; 12 } 13 14 public int Sample () 15 { 16 return Clamp (12 , 10 ); 17 } 18 } 19 }
View fixture on GitHub
# unknown-parameter-rejectedRefuses a parameter that neither exists nor has a type to add it with
refusal
Target symbol M:Shop.Pricing.Discount(System.Decimal,System.Int32)
Arguments parameters[{"name": "price"}, {"name": "rate"}]
Refuses unknown-parameter mentioning “rate”; every file is left unchanged
Pricing.cs input
1 namespace Shop ;2 3 public class Pricing 4 { 5 public decimal Discount (decimal price, int percent) 6 { 7 return price * percent / 100m ; 8 } 9 }
View fixture on GitHub