RefactorMCP catalog

Split If

Splits an if whose condition is joined by && or || into two if statements, the reverse of Merge Nested If and of Consolidate Conditional Expression.

Precondition

  • The caret is on an if statement.
  • The condition, ignoring enclosing parentheses, is an && or an ||.
  • For &&, the if has no else: splitting would need the else on both the outer and the inner if, duplicating it.

Transformation

The condition is split at its first operator, so a && b && c splits into a and b && c.

  • if (a && b) S becomes if (a) { if (b) S }.
  • if (a || b) S else E becomes if (a) S else if (b) S else E, duplicating S. This always preserves behaviour: b is only evaluated when a is false, and S runs at most once.
  • When there is no else and S always jumps away (it returns, throws, breaks or continues), the second if need not be an else: if (a || b) { return; } becomes if (a) { return; } followed by if (b) { return; }. Control only reaches the second if when a is false, exactly when b was evaluated before.

Preserved

  • Behaviour, including the order and short-circuiting of the conditions.
  • Pattern variables declared by the first operand stay in scope in the second and in the body.
  • Comments before the if stay before the first if.

Limitations

  • The split is always at the first operator; to split elsewhere, split repeatedly or add parentheses first.
  • Splitting on || duplicates the body. Consolidate it again with Extract Method if it is long.

Error codes

CodeMeaning
not-an-ifthe caret is not on an if statement
not-splittablethe condition is not joined by && or ``
and-with-elsethe condition is joined by && and the if has an else

Cases

·

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

#and-chain

A chain of && splits at its first operator, keeping the rest together

success
TargetOrder.cs, the /*^*/ caret
Order.csmodified
11 using System;
22
33 namespace Shop
44 {
55 public class Order
66 {
77 public void Ship(object item, int quantity, bool paid)
88 {
9− /*^*/if (item is string name && quantity > 0 && paid)
9+ if (item is string name)
1010 {
11− Console.WriteLine(name);
11+ if (quantity > 0 && paid)
12+ {
13+ Console.WriteLine(name);
14+ }
1215 }
1316 }
1417 }
1518 }

#and-into-nested

An if on a && b becomes an if on a holding an if on b

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 && paid)
9+ if (order != null)
1010 {
11− Console.WriteLine(order);
11+ if (paid)
12+ {
13+ Console.WriteLine(order);
14+ }
1215 }
1316 }
1417 }
1518 }

#and-with-else-rejected

Refuses an if on a && b with an else, which both ifs would need

refusal
TargetSample.cs, the /*^*/ caret
Refusesand-with-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 && b)
10 {
11 Console.WriteLine("both");
12 }
13 else
14 {
15 Console.WriteLine("not both");
16 }
17 }
18 }
19 }

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

#or-with-else

An if on a || b whose body can finish becomes an else if with the same body, keeping the else

success
TargetCounter.cs, the /*^*/ caret
Counter.csmodified
11 using System;
22
33 namespace Shop
44 {
55 public class Counter
66 {
77 private int _count;
88
99 public void Record(bool urgent, bool important)
1010 {
11− /*^*/if (urgent || important)
11+ if (urgent)
12+ {
13+ _count++;
14+ }
15+ else if (important)
1216 {
1317 _count++;
1418 }
1519 else
1620 {
1721 Console.WriteLine("ignored");
1822 }
1923 }
2024 }
2125 }

#or-with-jump

An if on a || b whose body always jumps away becomes two ifs with the same body, the comment staying on the first

success
TargetValidator.cs, the /*^*/ caret
Validator.csmodified
11 namespace Shop
22 {
33 public class Validator
44 {
55 public string Check(string name, int age)
66 {
77 // Reject what cannot be stored.
8− /*^*/if (name == null || age < 0)
8+ if (name == null)
9+ {
10+ return "invalid";
11+ }
12+
13+ if (age < 0)
914 {
1015 return "invalid";
1116 }
1217
1318 return "valid";
1419 }
1520 }
1621 }

#single-condition-rejected

Refuses a condition that is not && or ||

refusal
TargetSample.cs, the /*^*/ caret
Refusesnot-splittable; 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 }