RefactorMCP catalog

Invert If

Negates the condition of an if statement and swaps its branches.

Precondition

  • The caret is on an if statement, between if and the closing parenthesis of its condition.
  • When the if has no else, it must be in a block, and one of these holds:
  • Nothing follows it, and the block is the body of a method, function or accessor that returns nothing, or of a loop, so that running off its end is an implicit return or continue.
  • Code follows it, and its branch always jumps away (returns, throws, breaks or continues), so that the code after it only runs when the condition is false.

Transformation

  • The condition is replaced by its logical negation, in its most direct form: == becomes !=, < becomes >=, !x becomes x, x is T becomes x is not T, x is not null becomes x is null, and && and || follow De Morgan's laws, with parentheses added where precedence needs them. Anything else is negated with !.
  • With an else, the branches swap. An else if becomes the body of the new then branch, in braces, so that the new else cannot attach to it.
  • Without an else:
  • When nothing follows, the implicit exit becomes the new branch and the old branch follows the if, which introduces an early-return guard clause: if (c) { A(); } becomes if (!c) { return; } A();.
  • When the branch is exactly the implicit exit, such as return; at the end of a method returning nothing, the code after the if becomes its branch and the jump is dropped, which removes a guard clause.
  • Otherwise, when the code after the if also always jumps away, it becomes the branch and the old branch follows the if.

Preserved

  • The behaviour of every path through the statement.
  • Comments inside each branch move with that branch; comments before the if stay before it.

Limitations

  • Flipping an ordering comparison is only behaviour-preserving when the values are totally ordered. Comparisons of floating-point values (which may be NaN), nullable values (which may be null) and user-defined orderings are negated with !( ... ) instead. Equality is always flipped, assuming a user-defined != is the negation of its ==, as C# requires them in pairs.
  • An if without an else in a switch section, or ending a block that is not a method or loop body, is refused rather than rewritten with a goto or a break.

Error codes

CodeMeaning
not-an-ifthe caret is not on an if statement
code-followsthe if has no else and the code after it cannot become its branch, because the branch or that code can run on past the end
no-implicit-exitthe if has no else, nothing follows it, and its block does not end a method or loop body

Cases

·

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

#both-paths-jump

When the branch and the code after the if both leave the method, they swap places

success
TargetNames.cs, the /*^*/ caret
Projectnullable enable
Names.csmodified
11 using System;
22
33 namespace Shop
44 {
55 public class Names
66 {
77 public int Length(string? name)
88 {
9− /*^*/if (name is null)
9+ if (name is not null)
1010 {
11− throw new ArgumentNullException(nameof(name));
11+ return name.Length;
1212 }
1313
14− return name.Length;
14+ throw new ArgumentNullException(nameof(name));
1515 }
1616 }
1717 }

#code-follows-rejected

Refuses an if with no else whose branch can fall through to the code after it

refusal
TargetSample.cs, the /*^*/ caret
Refusescode-follows; every file is left unchanged
Sample.csinput
1 using System;
2
3 namespace Shop
4 {
5 public class Sample
6 {
7 public void Log(int value)
8 {
9 /*^*/if (value > 0)
10 {
11 Console.WriteLine("positive");
12 }
13
14 Console.WriteLine(value);
15 }
16 }
17 }

#comments-move-with-branches

Comments inside each branch move with it, and a negated call gains !

success
TargetStock.cs, the /*^*/ caret
Stock.csmodified
11 namespace Shop
22 {
33 public class Stock
44 {
55 public string Status(int level)
66 {
77 // Decide what to show.
8− /*^*/if (IsLow(level))
8+ if (!IsLow(level))
9+ {
10+ // Nothing to do.
11+ return "ok";
12+ }
13+ else
914 {
1015 // Ask for more.
1116 return "reorder";
12− }
13− else
14− {
15− // Nothing to do.
16− return "ok";
1717 }
1818 }
1919
2020 private bool IsLow(int level) => level < 5;
2121 }
2222 }

#de-morgan

Negating && and || follows De Morgan's laws, parenthesising where precedence needs it

success
TargetDiscount.cs, the /*^*/ caret
Discount.csmodified
11 namespace Shop
22 {
33 public class Discount
44 {
55 public decimal Rate(int quantity, bool member, string code)
66 {
7− /*^*/if (quantity >= 10 && !member || code == null)
7+ if ((quantity < 10 || member) && code != null)
88 {
9− return 0.1m;
9+ return 0m;
1010 }
1111 else
1212 {
13− return 0m;
13+ return 0.1m;
1414 }
1515 }
1616 }
1717 }

#else-if-chain

An else if becomes the body of the new then branch, in braces so the else cannot attach to it

success
TargetGrade.cs, the /*^*/ caret
Grade.csmodified
11 namespace Shop
22 {
33 public class Grade
44 {
55 public string Of(int score)
66 {
7− /*^*/if (score < 50)
7+ if (score >= 50)
88 {
9− return "fail";
10− }
11− else if (score < 80)
12− {
13− return "pass";
9+ if (score < 80)
10+ {
11+ return "pass";
12+ }
13+ else
14+ {
15+ return "merit";
16+ }
1417 }
1518 else
1619 {
17− return "merit";
20+ return "fail";
1821 }
1922 }
2023 }
2124 }

#embedded-statements

Branches without braces are swapped as they are, and a double negation is removed

success
TargetSign.cs, the /*^*/ caret
Sign.csmodified
11 namespace Shop
22 {
33 public class Sign
44 {
55 public int Of(bool negative)
66 {
7− /*^*/if (!negative)
7+ if (negative)
8+ return -1;
9+ else
810 return 1;
9− else
10− return -1;
1111 }
1212 }
1313 }

#floating-point-comparison

A comparison of values that may be NaN or null is negated with ! rather than flipped

success
TargetGauge.cs, the /*^*/ caret
Gauge.csmodified
11 namespace Shop
22 {
33 public class Gauge
44 {
55 public string Describe(double ratio, int? count)
66 {
7− /*^*/if (ratio > 0.5 && count < 3)
7+ if (!(ratio > 0.5) || !(count < 3))
88 {
9− return "high";
9+ return "low";
1010 }
1111 else
1212 {
13− return "low";
13+ return "high";
1414 }
1515 }
1616 }
1717 }

#guard-clause-continue

An if with no else that ends a loop body becomes an early continue

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)
99 {
1010 foreach (var quantity in quantities)
1111 {
12− /*^*/if (quantity > 0)
12+ if (quantity <= 0)
1313 {
14− Console.WriteLine(quantity);
14+ continue;
1515 }
16+
17+ Console.WriteLine(quantity);
1618 }
1719 }
1820 }
1921 }

#guard-clause-return

An if with no else that ends a void method becomes an early return

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)
88 {
99 Console.WriteLine("checking");
10− /*^*/if (order != null)
10+ if (order == null)
1111 {
12− Console.WriteLine("packing");
13− Console.WriteLine(order);
12+ return;
1413 }
14+
15+ Console.WriteLine("packing");
16+ Console.WriteLine(order);
1517 }
1618 }
1719 }

#no-implicit-exit-rejected

Refuses an if with no else that ends a block which does not end the method or a loop body

refusal
TargetSample.cs, the /*^*/ caret
Refusesno-implicit-exit; every file is left unchanged
Sample.csinput
1 using System;
2
3 namespace Shop
4 {
5 public class Sample
6 {
7 public int Log(int value)
8 {
9 if (value != 0)
10 {
11 /*^*/if (value > 0)
12 {
13 Console.WriteLine("positive");
14 }
15 }
16
17 return value;
18 }
19 }
20 }

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

#remove-guard-clause

An early return that ends a void method is inverted into an if around the code after it

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)
88 {
9− /*^*/if (order == null)
9+ if (order != null)
1010 {
11− return;
11+ // Only real orders are packed.
12+ Console.WriteLine("packing");
13+ Console.WriteLine(order);
1214 }
13−
14− // Only real orders are packed.
15− Console.WriteLine("packing");
16− Console.WriteLine(order);
1715 }
1816 }
1917 }

#simple

Negates the condition of an if with an else and swaps the branches

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 public string Describe(int value)
44 {
5− /*^*/if (value > 0)
5+ if (value <= 0)
6+ {
7+ return "not positive";
8+ }
9+ else
610 {
711 return "positive";
812 }
9− else
10− {
11− return "not positive";
12− }
1313 }
1414 }

#type-pattern

A type test becomes a negated pattern, keeping its designation

success
TargetShapes.cs, the /*^*/ caret
Shapes.csmodified
11 namespace Shop
22 {
33 public class Circle
44 {
55 public double Radius;
66 }
77
88 public class Shapes
99 {
1010 public double Area(object shape)
1111 {
12− /*^*/if (shape is Circle circle)
12+ if (shape is not Circle circle)
1313 {
14− return 3.14 * circle.Radius * circle.Radius;
14+ return 0;
1515 }
1616 else
1717 {
18− return 0;
18+ return 3.14 * circle.Radius * circle.Radius;
1919 }
2020 }
2121 }
2222 }