RefactorMCP catalog

Inline Local Variable

Replaces every use of a local with the expression it was initialised with, and removes the declaration. The reverse of Extract Local Variable.

Target

The local, by a caret on its declaration or on any use of it. In a later step of a composite, the containing member's symbol with arguments.local naming the local.

Precondition

  • The local is declared by a local declaration statement in a block or switch section, with an initializer, and is not a using declaration or a ref local.
  • The local is never written after its declaration: not assigned, incremented, or passed by out or ref.
  • The local is used at least once.
  • When it is used more than once, the initializer has no side effects: no call, object creation, assignment, increment or await.
  • No local, parameter or field the initializer reads is assigned between the declaration and a use. A use inside a loop that does not contain the declaration counts the whole loop as between.

Transformation

  • Each use is replaced by the initializer, parenthesised only where precedence requires it.
  • When the declaration converted the value to the local's type, the conversion is kept as a cast wherever removing it would change the meaning, so double ratio = count; inlines as (double)count.
  • A target-typed new() or an array initializer names the local's type, since it no longer has a declaration to take it from.
  • The declaration is removed. When the statement declares other locals too, only this one is removed from it.

Preserved

  • Behaviour: each use sees the value the local held.
  • Comments above the declaration stay in place, above the statement that followed it.

Limitations

  • A single use of an initializer with side effects moves those side effects to the point of use, past the statements in between. Only variables the initializer reads directly are checked for assignments in between, not state a call inside it might read.
  • Locals declared by for, using, foreach or pattern statements are not covered.

Error codes

CodeMeaning
no-initializerthe local has no value to inline
assigned-after-declarationthe local is written after its declaration
initializer-has-side-effectsthe initializer has side effects and the local is used more than once
initializer-inputs-changea variable the initializer reads is assigned before a use
never-usedthe local is never read; Safe Delete Local removes it
using-declarationthe local is a using declaration and would no longer be disposed
not-a-localthe target is not a local variable

Cases

·

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

#assigned-after-declaration-rejected

Refuses a local that is assigned again, whose uses see different values

refusal
TargetSample.cs, the /*^*/ caret
Refusesassigned-after-declaration; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Next(int start)
4 {
5 var /*^*/current = start;
6 current++;
7 return current;
8 }
9 }

#initializer-inputs-change-rejected

Refuses when a variable the initializer reads is assigned before a use

refusal
TargetSample.cs, the /*^*/ caret
Refusesinitializer-inputs-change; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 private int _count;
4
5 public int Reset(int count)
6 {
7 var /*^*/previous = count;
8 count = 0;
9 _count = count;
10 return previous;
11 }
12 }

#keeps-conversion

An implicit conversion the declaration performed is kept as a cast, so arithmetic keeps its meaning

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 public double Half(int count)
44 {
5− double /*^*/ratio = count;
6− return ratio / 2;
5+ return (double)count / 2;
76 }
87 }

#never-used-rejected

Refuses a local that is never read, leaving its removal to Safe Delete Local

refusal
TargetSample.cs, the /*^*/ caret
Refusesnever-used; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Zero(int a)
4 {
5 var /*^*/unused = a + 1;
6 return 0;
7 }
8 }

#no-initializer-rejected

Refuses a local declared without a value

refusal
TargetSample.cs, the /*^*/ caret
Refusesno-initializer; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Pick(bool first)
4 {
5 int /*^*/choice;
6 choice = first ? 1 : 2;
7 return choice;
8 }
9 }

#nullable-context

Inlines a nullable local into a null-coalescing expression without new warnings

success
TargetSample.cs, the /*^*/ caret
Projectnullable enable
Sample.csmodified
11 public class Sample
22 {
33 public string Describe(int id)
44 {
5− string? /*^*/name = Find(id);
6− return name ?? "unknown";
5+ return Find(id) ?? "unknown";
76 }
87
98 private string? Find(int id) => id > 0 ? "found" : null;
109 }

#one-of-several-declarators

Inlines one local of a declaration that declares several, leaving the others

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 public int Area(int width)
44 {
5− int /*^*/height = 3, depth = 4;
6− return width * height * depth;
5+ int depth = 4;
6+ return width * 3 * depth;
77 }
88 }

#parameter-rejected

Refuses a parameter, which is not a local

refusal
TargetSample.cs, the /*^*/ caret
Refusesnot-a-local; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Twice(int /*^*/value)
4 {
5 return value * 2;
6 }
7 }

#several-uses-keep-comments

Replaces every use of a side-effect free initializer; the comment above the declaration stays in place

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public bool IsLarge(int price, int quantity, int limit)
66 {
77 // Price before tax.
8− var /*^*/net = price * quantity;
9− Console.WriteLine(net);
10− return net > limit;
8+ Console.WriteLine(price * quantity);
9+ return price * quantity > limit;
1110 }
1211 }

#side-effects-rejected

Refuses to repeat an initializer with side effects at several uses

refusal
TargetSample.cs, the /*^*/ caret
Refusesinitializer-has-side-effects; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 private int _counter;
4
5 public int Twice()
6 {
7 var /*^*/next = Next();
8 return next + next;
9 }
10
11 private int Next() => ++_counter;
12 }

#single-use

Replaces the only use with the initializer, parenthesised where precedence needs it, and removes the declaration

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 public int Sum(int a, int b)
44 {
5− var /*^*/total = a + b;
6− return total * 2;
5+ return (a + b) * 2;
76 }
87 }

#target-typed-new

A target-typed new names its type once it no longer has a declaration to take it from

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System.Collections.Generic;
22
33 public class Sample
44 {
55 public int Count()
66 {
7− Dictionary<string, int> /*^*/counts = new();
8− return Fill(counts);
7+ return Fill(new Dictionary<string, int>());
98 }
109
1110 private int Fill(Dictionary<string, int> counts)
1211 {
1312 counts["a"] = 1;
1413 return counts.Count;
1514 }
1615 }

#using-declaration-rejected

Refuses a using declaration, which would no longer be disposed

refusal
TargetSample.cs, the /*^*/ caret
Refusesusing-declaration; every file is left unchanged
Sample.csinput
1 using System.IO;
2
3 public class Sample
4 {
5 public int Length(string text)
6 {
7 using var /*^*/reader = new StringReader(text);
8 return reader.ReadToEnd().Length;
9 }
10 }