RefactorMCP catalog

Merge Nested If

Joins an if whose only statement is another if into a single if on both conditions: if (a) { if (b) { S } } becomes if (a && b) { S }.

Precondition

  • The caret is on the outer if statement.
  • The outer if has no else: it would otherwise also run when a holds and b does not.
  • The outer if's body is the inner if and nothing else, with or without braces.
  • The inner if has no else: it would otherwise also run when a does not hold.

Transformation

  • The conditions are joined with &&, outer first, so they are evaluated in the same order and b is still only evaluated when a holds. A condition that binds more loosely than &&, such as one using ||, is parenthesised.
  • The inner if's body becomes the body of the merged if.

Preserved

  • Behaviour, including short-circuiting: b is evaluated exactly when it was.
  • Pattern variables declared by the outer condition stay in scope in the inner condition and the body.
  • Comments above the outer if; comments above the inner if move to the top of the merged body, inside the braces they were in.

Limitations

  • Only a directly nested if is merged; an inner if inside a nested block or after a declaration is not.

Error codes

CodeMeaning
not-an-ifthe caret is not on an if statement
outer-has-elsethe outer if has an else
inner-has-elsethe inner if has an else
not-only-statementthe outer if's body has statements besides the inner if
no-inner-ifthe outer if's body is not an if statement

Cases

·

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

#comments-kept

Comments on the inner if and inside its body are kept in the merged body

success
TargetStock.cs, the /*^*/ caret
Stock.csmodified
11 using System;
22
33 namespace Shop
44 {
55 public class Stock
66 {
77 public void Check(object item, int level)
88 {
99 // Only products are counted.
10− /*^*/if (item is Product product)
10+ if (item is Product product && level < product.Minimum)
1111 {
1212 // Low stock needs attention.
13− if (level < product.Minimum)
14− {
15− Console.WriteLine(product.Name); // reorder
16− }
13+ Console.WriteLine(product.Name); // reorder
1714 }
1815 }
1916 }
2017
2118 public class Product
2219 {
2320 public string Name = "";
2421 public int Minimum;
2522 }
2623 }

#inner-else-rejected

Refuses an inner if with an else, which would then run when the outer condition fails

refusal
TargetSample.cs, the /*^*/ caret
Refusesinner-has-else; every file is left unchanged
Sample.csinput
1 using System;
2
3 namespace Shop
4 {
5 public class Sample
6 {
7 public void Log(bool a, bool b)
8 {
9 /*^*/if (a)
10 {
11 if (b)
12 {
13 Console.WriteLine("both");
14 }
15 else
16 {
17 Console.WriteLine("only a");
18 }
19 }
20 }
21 }
22 }

#no-inner-if-rejected

Refuses an if whose body holds no if to merge

refusal
TargetSample.cs, the /*^*/ caret
Refusesno-inner-if; every file is left unchanged
Sample.csinput
1 using System;
2
3 namespace Shop
4 {
5 public class Sample
6 {
7 public void Log(bool a)
8 {
9 /*^*/if (a)
10 {
11 Console.WriteLine("a");
12 }
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 }

#other-statements-rejected

Refuses an outer if whose body does more than the inner if

refusal
TargetSample.cs, the /*^*/ caret
Refusesnot-only-statement; every file is left unchanged
Sample.csinput
1 using System;
2
3 namespace Shop
4 {
5 public class Sample
6 {
7 public void Log(bool a, bool b)
8 {
9 /*^*/if (a)
10 {
11 Console.WriteLine("a");
12 if (b)
13 {
14 Console.WriteLine("both");
15 }
16 }
17 }
18 }
19 }

#outer-else-rejected

Refuses an outer if with an else, which would then run when only the inner condition fails

refusal
TargetSample.cs, the /*^*/ caret
Refusesouter-has-else; every file is left unchanged
Sample.csinput
1 using System;
2
3 namespace Shop
4 {
5 public class Sample
6 {
7 public void Log(bool a, bool b)
8 {
9 /*^*/if (a)
10 {
11 if (b)
12 {
13 Console.WriteLine("both");
14 }
15 }
16 else
17 {
18 Console.WriteLine("not a");
19 }
20 }
21 }
22 }

#parenthesises-or

An || condition is parenthesised when it becomes an operand of &&

success
TargetDiscount.cs, the /*^*/ caret
Discount.csmodified
11 namespace Shop
22 {
33 public class Discount
44 {
55 public decimal Rate(bool member, bool student, int quantity)
66 {
7− /*^*/if (member || student)
7+ if ((member || student) && (quantity > 10 || quantity < 0))
88 {
9− if (quantity > 10 || quantity < 0)
10− {
11− return 0.1m;
12− }
9+ return 0.1m;
1310 }
1411
1512 return 0m;
1613 }
1714 }
1815 }

#simple

An if whose only statement is another if becomes one if on both conditions

success
TargetShipping.cs, the /*^*/ caret
Shipping.csmodified
11 using System;
22
33 namespace Shop
44 {
55 public class Shipping
66 {
77 public void Ship(string order, bool paid)
88 {
9− /*^*/if (order != null)
9+ if (order != null && paid)
1010 {
11− if (paid)
12− {
13− Console.WriteLine(order);
14− }
11+ Console.WriteLine(order);
1512 }
1613 }
1714 }
1815 }

#without-braces

Nested ifs without braces merge, keeping the embedded statement

success
TargetLog.cs, the /*^*/ caret
Log.csmodified
11 using System;
22
33 namespace Shop
44 {
55 public class Log
66 {
77 public void Write(string message, bool verbose)
88 {
9− /*^*/if (verbose)
10− if (message.Length > 0)
11− Console.WriteLine(message);
9+ if (verbose && message.Length > 0)
10+ Console.WriteLine(message);
1211 }
1312 }
1413 }