RefactorMCP catalog

Safe Delete Local

Deletes a local variable that nothing uses, keeping whatever its initializer did beyond producing a value.

Precondition

  • The caret is on a local declared by a local declaration statement.
  • Nothing reads or assigns the local after its declaration.
  • The local is not a using declaration, whose disposal at the end of the scope is behaviour.

Transformation

  • A declaration whose initializer has no side effects is removed. Comments above it stay, above the statement that follows.
  • An initializer with side effects, one that calls a method, creates an object, assigns, awaits or indexes, is kept:
  • as a statement of its own when C# allows one, so var loaded = Load(); becomes Load();;
  • otherwise as a discard, so var total = Load() + 1; becomes _ = Load() + 1;.
  • A local declared alongside others loses just its declarator. When its initializer is kept, the declaration is split around it so every initializer still runs in the order written: int first = Next(), second = Next(), third = Next(); becomes three statements.

Preserved

  • The order and number of evaluations of every initializer.
  • Comments above the declaration.

Limitations

  • Property reads are treated as free of side effects, so var count = list.Count; is removed outright.
  • A local that is only written, never read, is refused rather than deleted with its assignments.
  • Locals declared by for, foreach, using statements, patterns and out arguments are not offered.

Error codes

CodeMeaning
local-referencedthe local is read or assigned after its declaration
using-declarationthe local is a using declaration
not-a-localthe caret is not on 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-later-refused

Refuses to delete a local that is assigned after its declaration

refusal
TargetSample.cs, the /*^*/ caret
Refuseslocal-referenced; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public void Run(int a)
4 {
5 var /*^*/last = 0;
6 last = a;
7 }
8 }

#call-initializer-kept

Keeps a call that initialised the deleted local as a statement, with the comment above it

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 private int _loads;
44
55 public int Run()
66 {
77 // Warm the cache.
8− int /*^*/loaded = Load();
8+ Load();
99
1010 return _loads;
1111 }
1212
1313 private int Load() => ++_loads;
1414 }

#expression-initializer-discarded

Keeps an initializer with side effects that is not a statement on its own as a discard assignment

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 private int _loads;
44
55 public int Run()
66 {
7− var /*^*/total = Load() + 1;
7+ _ = Load() + 1;
88 return _loads;
99 }
1010
1111 private int Load() => ++_loads;
1212 }

#local-referenced-refused

Refuses to delete a local that is read

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

#not-a-local-refused

Refuses when the caret is on a parameter rather than 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) => 2;
4 }

#nullable-local

Deletes an unused nullable local, keeping the lookup that initialised it

success
TargetSample.cs, the /*^*/ caret
Projectnullable enable
Sample.csmodified
11 using System.Collections.Generic;
22
33 public class Sample
44 {
55 private readonly Dictionary<string, string> _names = new Dictionary<string, string>();
66
77 public int Count(string key)
88 {
9− string? /*^*/name = Find(key);
9+ Find(key);
1010 return _names.Count;
1111 }
1212
1313 private string? Find(string key) => _names.TryGetValue(key, out var value) ? value : null;
1414 }

#one-of-several-declarators

Deletes one local from a declaration of several, keeping its call between the others in order

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 private int _next;
44
55 public int Run()
66 {
7− int first = Next(), /*^*/second = Next(), third = Next();
7+ int first = Next();
8+ Next();
9+ int third = Next();
810 return first + third;
911 }
1012
1113 private int Next() => ++_next;
1214 }

#unused-local

Deletes a local that is never read, whose initializer has no side effects

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

#using-declaration-refused

Refuses to delete a using declaration, whose disposal is behaviour

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 void Run()
6 {
7 using var /*^*/stream = new MemoryStream();
8 }
9 }