RefactorMCP catalog

Remove Redundant Else

Removes the else of an if whose branch always jumps away, so the else's statements follow the if: if (a) { return A(); } else { B(); } becomes if (a) { return A(); } followed by B();.

Target

The if, by caret: "target": { "file": "Payroll.cs", "caret": "marker" }.

Precondition

  • The caret is on an if statement, between if and the closing parenthesis of its condition, and the if has an else.
  • The if is a statement of a block or switch section, so statements can follow it. An else if is the else of the if before it, not such a statement.
  • The if's branch always jumps away: it returns, throws, breaks, continues or otherwise never reaches its end. The else's statements then only run when the condition is false, wherever they are.
  • No local the else declares is declared elsewhere in the block it joins, including in the if's own branch, since the two would then share a scope.

Transformation

  • The else is removed from the if, and its statements follow the if in the block, the first set apart from it by a blank line. An else without braces contributes its one statement; an else if becomes an if of its own, with the rest of its chain.
  • The condition and the branch are unchanged.

Preserved

  • The behaviour of every path through the statement.
  • Comments above the else keyword and inside the else move with its statements, above the first of them; blank lines between them are kept.

Limitations

  • An else whose statements declare a local that is also declared elsewhere in the enclosing block is refused even when the two would not actually clash, such as one in a nested block of the else.
  • An if with no else whose branch jumps away is left alone; there is nothing to remove.

Error codes

CodeMeaning
not-an-ifthe caret is not on an if statement
no-elsethe if has no else
not-in-blockthe if is not a statement of a block or switch section, such as an else if
branch-falls-throughthe if's branch can run on past its end
name-conflicta local the else declares is declared elsewhere in the block it would join

Cases

·

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

#branch-falls-through-rejected

Refuses an if whose branch can run on past its end, since the else's statements would then run after it

refusal
TargetSample.cs, the /*^*/ caret
Refusesbranch-falls-through; every file is left unchanged
Sample.csinput
1 using System;
2
3 namespace Shop
4 {
5 public class Sample
6 {
7 public void Report(int value)
8 {
9 /*^*/if (value < 0)
10 {
11 Console.WriteLine("negative");
12 if (value < -100)
13 {
14 return;
15 }
16 }
17 else
18 {
19 Console.WriteLine("positive");
20 }
21 }
22 }
23 }

#comments-kept

Comments before the else keyword and inside the else move with its statements, and a blank line inside it is kept

success
TargetStock.cs, the /*^*/ caret
Stock.csmodified
11 using System;
22
33 namespace Shop
44 {
55 public class Stock
66 {
77 public int Reorder(int level, int minimum)
88 {
99 // Nothing to do when stocked.
10− /*^*/if (level >= minimum)
10+ if (level >= minimum)
1111 {
1212 return 0; // stocked
1313 }
14+
1415 // Below the minimum.
15− else
16− {
17− // Order enough to reach it.
18− var shortfall = minimum - level;
19− Console.WriteLine(shortfall);
16+ // Order enough to reach it.
17+ var shortfall = minimum - level;
18+ Console.WriteLine(shortfall);
2019
21− return shortfall; // ordered
22− }
20+ return shortfall; // ordered
2321 }
2422 }
2523 }

#else-if

An else if after a branch that always throws becomes an if of its own, keeping the rest of the chain

success
TargetGrading.cs, the /*^*/ caret
Grading.csmodified
11 using System;
22
33 namespace School
44 {
55 public class Grading
66 {
77 public void Report(int score)
88 {
9− /*^*/if (score < 0)
9+ if (score < 0)
1010 {
1111 throw new ArgumentOutOfRangeException(nameof(score));
1212 }
13− else if (score >= 50)
13+
14+ if (score >= 50)
1415 {
1516 Console.WriteLine("pass");
1617 }
1718 else
1819 {
1920 Console.WriteLine("fail");
2021 }
2122
2223 Console.WriteLine(score);
2324 }
2425 }
2526 }

#embedded-statements

Without braces, the else's statement follows the if as it is

success
TargetSign.cs, the /*^*/ caret
Sign.csmodified
11 namespace Maths
22 {
33 public class Sign
44 {
55 public string Describe(int value)
66 {
7− /*^*/if (value < 0)
7+ if (value < 0)
88 return "negative";
9− else
10− return value == 0 ? "zero" : "positive";
9+
10+ return value == 0 ? "zero" : "positive";
1111 }
1212 }
1313 }

#loop-and-switch

A branch that continues a loop jumps away too, and the if can sit in a switch section

success
TargetBatch.cs, the /*^*/ caret
Batch.csmodified
11 using System;
22 using System.Collections.Generic;
33
44 namespace Shop
55 {
66 public class Batch
77 {
88 public void Process(List<int> quantities, int mode)
99 {
1010 foreach (var quantity in quantities)
1111 {
1212 switch (mode)
1313 {
1414 case 1:
15− /*^*/if (quantity == 0)
15+ if (quantity == 0)
1616 {
1717 continue;
1818 }
19− else
20− {
21− Console.WriteLine(quantity);
22− }
19+
20+ Console.WriteLine(quantity);
2321
2422 break;
2523 default:
2624 Console.WriteLine(mode);
2725 break;
2826 }
2927 }
3028 }
3129 }
3230 }

#name-conflict-rejected

Refuses when a local the else declares would share the enclosing block with a local of the same name declared elsewhere in it

refusal
TargetPayroll.cs, the /*^*/ caret
Refusesname-conflict mentioning “bonus”; every file is left unchanged
Payroll.csinput
1 namespace Staff
2 {
3 public class Payroll
4 {
5 public decimal Pay(bool retired, decimal salary)
6 {
7 /*^*/if (retired)
8 {
9 var bonus = 100m;
10 return bonus;
11 }
12 else
13 {
14 var bonus = salary * 0.1m;
15 return salary + bonus;
16 }
17 }
18 }
19 }

#no-else-rejected

Refuses an if that has no else

refusal
TargetSample.cs, the /*^*/ caret
Refusesno-else; every file is left unchanged
Sample.csinput
1 namespace Shop
2 {
3 public class Sample
4 {
5 public int Clamp(int value)
6 {
7 /*^*/if (value < 0)
8 {
9 return 0;
10 }
11
12 return value;
13 }
14 }
15 }

#not-an-if-rejected

Refuses a caret that is not on an if statement

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

#not-in-block-rejected

Refuses an else if, which is the else of the if before it rather than a statement of a block, so nothing can follow it

refusal
TargetGrading.cs, the /*^*/ caret
Refusesnot-in-block; every file is left unchanged
Grading.csinput
1 namespace School
2 {
3 public class Grading
4 {
5 public string Grade(int score, bool resit)
6 {
7 if (resit)
8 {
9 score -= 10;
10 }
11 else /*^*/if (score >= 90)
12 {
13 return "A";
14 }
15 else
16 {
17 score += 5;
18 }
19
20 return score >= 50 ? "pass" : "fail";
21 }
22 }
23 }

#nullable-context

With nullable annotations, the statements that follow the if keep the null state the else had

success
TargetNames.cs, the /*^*/ caret
Projectnullable enable
Names.csmodified
11 namespace People
22 {
33 public class Names
44 {
55 public string Initial(string? name)
66 {
7− /*^*/if (name is null or "")
7+ if (name is null or "")
88 {
99 return "?";
1010 }
11− else
12− {
13− return name.Substring(0, 1);
14− }
11+
12+ return name.Substring(0, 1);
1513 }
1614 }
1715 }

#simple

The else after a branch that always returns is removed, and its statements follow the if

success
TargetPayroll.cs, the /*^*/ caret
Payroll.csmodified
11 namespace Staff
22 {
33 public class Payroll
44 {
55 public decimal Pay(bool retired, decimal salary)
66 {
7− /*^*/if (retired)
7+ if (retired)
88 {
99 return 0m;
1010 }
11− else
12− {
13− var bonus = salary * 0.1m;
14− return salary + bonus;
15− }
11+
12+ var bonus = salary * 0.1m;
13+ return salary + bonus;
1614 }
1715 }
1816 }