RefactorMCP catalog

Split Declaration and Assignment

Turns a local declaration with an initializer into a declaration without one, followed by an assignment of the same value: var x = e; becomes T x; and x = e;. The reverse of Join Declaration and Assignment.

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.
  • The statement declares only that local.
  • The local is not const, not a using declaration and not a ref local.
  • A var local's type can be named, so it is not of an anonymous type.

Transformation

  • The declaration keeps its written type, or var is replaced by the inferred type, written as briefly as the scope allows. In a nullable context the type carries the annotation var implied, so a reference type is written as nullable.
  • The assignment follows the declaration directly.
  • An array initializer, which is only valid in a declaration, becomes an array creation of the declared type.

Preserved

  • Behaviour: the value is computed at the same point.
  • Comments above the declaration stay above it; a comment at the end of the line follows the value into the assignment.

Limitations

  • Locals declared by for, using, foreach or pattern statements are not covered.

Error codes

CodeMeaning
no-initializerthe declaration has no value to split off
multiple-declaratorsthe statement declares several locals, whose initializers would run out of order
const-localthe local is a constant, which cannot be assigned
using-declarationthe local is a using declaration, whose disposal is tied to its initializer
anonymous-typethe local's type is anonymous and cannot be written
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.

#anonymous-type-rejected

Refuses a local of anonymous type, which has no name to declare it with

refusal
TargetSample.cs, the /*^*/ caret
Refusesanonymous-type; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public string Describe()
4 {
5 var /*^*/point = new { X = 1, Y = 2 };
6 return point.X + "," + point.Y;
7 }
8 }

#array-initializer

An array initializer, which is only valid in a declaration, becomes an array creation

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 public int First()
44 {
5− int[] /*^*/values = { 1, 2, 3 };
5+ int[] values;
6+ values = new int[] { 1, 2, 3 };
67 return values[0];
78 }
89 }

#const-local-rejected

Refuses a constant, which cannot be assigned

refusal
TargetSample.cs, the /*^*/ caret
Refusesconst-local; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Limit()
4 {
5 const int /*^*/max = 10;
6 return max;
7 }
8 }

#generic-type

The inferred type is written with the method's type parameters

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System.Collections.Generic;
22
33 public class Sample
44 {
55 public List<T> Twice<T>(T item)
66 {
7− var /*^*/items = new List<T>();
7+ List<T> items;
8+ items = new List<T>();
89 items.Add(item);
910 items.Add(item);
1011 return items;
1112 }
1213 }

#keeps-comments

A comment above the declaration stays on it, and a trailing comment follows the value into the assignment

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public void Run(string name)
66 {
77 Console.WriteLine("Start");
88
99 // The greeting to show.
10− string /*^*/greeting = "Hello " + name; // built once
10+ string greeting;
11+ greeting = "Hello " + name; // built once
1112 Console.WriteLine(greeting);
1213 }
1314 }

#multiple-declarators-rejected

Refuses a declaration of several locals, whose initialisers would run out of order

refusal
TargetSample.cs, the /*^*/ caret
Refusesmultiple-declarators; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Sum()
4 {
5 int /*^*/a = 1, b = 2;
6 return a + b;
7 }
8 }

#no-initializer-rejected

Refuses a declaration that has no value to split off

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

In a nullable context the declared type keeps the nullable annotation var implied

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

#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 }

#using-declaration-rejected

Refuses a using declaration, whose disposal is tied to its initialiser

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 }

#var-declaration

Splits a var declaration into a declaration with the inferred type and an assignment

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;
5+ int total;
6+ total = a + b;
67 return total * 2;
78 }
89 }