RefactorMCP catalog

Split Temporary Variable

Gives a local that is reassigned for an unrelated purpose a new local from that reassignment on. The assignment becomes the declaration of a new local, and the uses after it refer to the new local. Applied once per reassignment, it leaves one local per purpose.

Target

The reassignment, by a caret on the assigned local in x = e;. The argument name is the new local's name.

Precondition

  • The caret is on the left of a plain assignment statement to a local that is declared by a local declaration statement.
  • The assignment is in the same block as the declaration, so it runs before every statement after it in that block and no later code can read the earlier value.
  • The assigned value does not read the local, so it is unrelated to the old value. A compound assignment such as x += 1 reads it.
  • The local is not captured by a lambda or local function, which could read it after the assignment.
  • name is not visible at the assignment and is not declared as a local or parameter anywhere in the member.

Transformation

  • The assignment becomes a declaration of name with the same value. An explicitly typed local's type is repeated; a var local's new local is var when the value has the local's type by itself, and is declared with the local's type otherwise.
  • Every use of the local after the assignment, including later assignments, refers to name. Uses before it keep the original local.

Preserved

  • Behaviour: each use reads the same value it did.
  • Comments around the assignment, and the nullable annotation of an explicit type.

Limitations

  • Splits at one assignment at a time. A local with three purposes takes two applications.
  • Locals written through ref aliases are not detected.

Error codes

CodeMeaning
not-an-assignmentthe caret is not on the target of a plain assignment statement
assignment-reads-variablethe assignment reads the local, or is a compound assignment
assignment-in-nested-blockthe assignment is in a block nested inside the declaring one
captured-variablethe local is captured by a lambda or local function
name-conflictname is already declared or visible where the new local would be
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.

#assignment-in-nested-block-rejected

Refuses a reassignment in a nested block, after which either value may be read

refusal
TargetSample.cs, the /*^*/ caret
Arguments
name"other"
Refusesassignment-in-nested-block; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public void Print(int a, bool reset)
6 {
7 int value = a;
8 if (reset)
9 {
10 /*^*/value = 0;
11 }
12
13 Console.WriteLine(value);
14 }
15 }

#assignment-reads-variable-rejected

Refuses a reassignment that reads the old value, which is not an unrelated purpose

refusal
TargetSample.cs, the /*^*/ caret
Arguments
name"doubled"
Refusesassignment-reads-variable; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public void Print(int a)
6 {
7 int value = a;
8 Console.WriteLine(value);
9 /*^*/value = value * 2;
10 Console.WriteLine(value);
11 }
12 }

#captured-variable-rejected

Refuses a local captured by a lambda, which may read it after the reassignment

refusal
TargetSample.cs, the /*^*/ caret
Arguments
name"other"
Refusescaptured-variable; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public void Print(int a)
6 {
7 int value = a;
8 Action print = () => Console.WriteLine(value);
9 print();
10 /*^*/value = 0;
11 print();
12 }
13 }

#compound-assignment-rejected

Refuses a compound assignment, which reads the old value

refusal
TargetSample.cs, the /*^*/ caret
Arguments
name"more"
Refusesassignment-reads-variable; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public void Print(int a)
6 {
7 int value = a;
8 /*^*/value += 1;
9 Console.WriteLine(value);
10 }
11 }

#later-assignments-follow

Every use after the split point, including later assignments, moves to the new local; the uses before keep the old one

success
TargetSample.cs, the /*^*/ caret
Arguments
name"second"
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public void Run(int a, int b)
66 {
77 int value = a;
88 Console.WriteLine(value);
9− /*^*/value = b;
10− Console.WriteLine(value);
9+ int second = b;
10+ Console.WriteLine(second);
1111 if (a > b)
1212 {
13− value = a - b;
13+ second = a - b;
1414 }
1515
16− Console.WriteLine(value);
16+ Console.WriteLine(second);
1717 }
1818 }

#name-conflict-rejected

Refuses a new name that is already a local or parameter of the method

refusal
TargetSample.cs, the /*^*/ caret
Arguments
name"a"
Refusesname-conflict; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public void Print(int a, int b)
6 {
7 int value = a;
8 Console.WriteLine(value);
9 /*^*/value = b;
10 Console.WriteLine(value);
11 }
12 }

#not-an-assignment-rejected

Refuses a caret on a use that is not a reassignment of the local

refusal
TargetSample.cs, the /*^*/ caret
Arguments
name"other"
Refusesnot-an-assignment; every file is left unchanged
Sample.csinput
1 using System;
2
3 public class Sample
4 {
5 public void Print(int a)
6 {
7 int value = a;
8 Console.WriteLine(/*^*/value);
9 }
10 }

#nullable-context

The new local keeps the nullable annotation of the declared type

success
TargetSample.cs, the /*^*/ caret
Arguments
name"backup"
Projectnullable enable
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public void Show(int id)
66 {
77 string? label = Find(id);
88 Console.WriteLine(label ?? "none");
9− /*^*/label = Find(id + 1);
10− Console.WriteLine(label ?? "none");
9+ string? backup = Find(id + 1);
10+ Console.WriteLine(backup ?? "none");
1111 }
1212
1313 private string? Find(int id) => id > 0 ? "found" : null;
1414 }

#parameter-rejected

Refuses a parameter, which is not a local

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

#second-assignment

A local reused for an unrelated value gets a new local from the reassignment on

success
TargetSample.cs, the /*^*/ caret
Arguments
name"area"
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public void Print(double height, double width)
66 {
77 double temp = 2 * (height + width);
88 Console.WriteLine(temp);
9− /*^*/temp = height * width;
10− Console.WriteLine(temp);
9+ double area = height * width;
10+ Console.WriteLine(area);
1111 }
1212 }

#var-keeps-comments

A var local splits into another var local, and the comments around the reassignment stay

success
TargetSample.cs, the /*^*/ caret
Arguments
name"farewell"
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public void Greet(string name)
66 {
77 var text = "Hello " + name;
88 Console.WriteLine(text);
99
1010 // Now say goodbye.
11− /*^*/text = "Goodbye " + name; // shown last
12− Console.WriteLine(text);
11+ var farewell = "Goodbye " + name; // shown last
12+ Console.WriteLine(farewell);
1313 }
1414 }

#var-with-different-type

When the new value's own type differs from the local's, the new local is declared with the local's type

success
TargetSample.cs, the /*^*/ caret
Arguments
name"fallback"
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public void Show(object first)
66 {
77 var shown = first;
88 Console.WriteLine(shown);
9− /*^*/shown = "none";
10− Console.WriteLine(shown);
9+ object fallback = "none";
10+ Console.WriteLine(fallback);
1111 }
1212 }