Operators (C#)
This page covers arithmetic, comparison, and logical operators in C#. C# provides a rich set of operators, with some behaviors that differ from other languages — such as integer division truncating the result and the + operator concatenating strings. Be sure to also understand how these operators interact with Nullable types and bitwise operations.
Arithmetic Operators
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ | Addition. Concatenation when used with strings. | 850 + 150 | 1000 |
- | Subtraction. | 850 - 50 | 800 |
* | Multiplication. | 850 * 2 | 1700 |
/ | Division. When both operands are integers, the result is also an integer (decimal part is truncated). | 850 / 4 | 212 |
% | Remainder (modulo). | 850 % 7 | 3 |
++ | Increment. Increases the value by 1. | level++ | level + 1 |
-- | Decrement. Decreases the value by 1. | level-- | level - 1 |
When both operands of / are integers, the result is also an integer. For example, 850 / 4 produces 212, not 212.5. If you need a floating-point result, cast one of the operands to double or float (e.g., (double)850 / 4).
The + operator on string performs string concatenation. When you join a number and a string with +, the number is automatically converted to a string and concatenated (e.g., "Score: " + 850 → "Score: 850").
Comparison Operators
| Operator | Meaning | Example | Result |
|---|---|---|---|
== | Equal to. | score == 850 | true / false |
!= | Not equal to. | score != 0 | true / false |
> | Greater than. | score > 850 | true / false |
>= | Greater than or equal to. | score >= 850 | true / false |
< | Less than. | score < 850 | true / false |
<= | Less than or equal to. | score <= 850 | true / false |
All comparison operators return a bool value (true or false). For string, the == operator compares by value (content), not by reference. This is because C#'s string overloads the == operator, so you do not need a special method like Java's .equals().
Logical Operators
| Operator | Meaning | Description |
|---|---|---|
&& | Logical AND | Returns true when both the left and right operands are true. If the left operand is false, the right operand is not evaluated (short-circuit evaluation). |
|| | Logical OR | Returns true when either the left or right operand is true. If the left operand is true, the right operand is not evaluated (short-circuit evaluation). |
! | Logical NOT | Inverts the value: true becomes false, and false becomes true. |
&& and || use short-circuit evaluation. With &&, evaluation stops as soon as the left operand is false. With ||, evaluation stops as soon as the left operand is true. This behavior is commonly used to perform a null check first and then safely access a member (e.g., obj != null && obj.IsValid).
Bitwise Operators
| Operator | Meaning | Example | Result (binary) |
|---|---|---|---|
& | Bitwise AND | 0b1010 & 0b1100 | 0b1000 (8) |
| | Bitwise OR | 0b1010 | 0b1100 | 0b1110 (14) |
^ | Bitwise XOR | 0b1010 ^ 0b1100 | 0b0110 (6) |
~ | Bitwise NOT | ~0b00000001 | All bits inverted. |
<< | Left shift | 1 << 3 | 8 (2³) |
>> | Right shift | 8 >> 1 | 4 |
Bitwise operators are primarily used for flag management (combining the [Flags] attribute with enum) and low-level processing where performance matters. In game development, they are useful for managing multiple state flags packed into a single integer.
Nullable Types and Operators
When using arithmetic or comparison operators with Nullable types such as int?, be careful: if a Nullable variable is null, arithmetic operations on it also produce null. For comparisons, any comparison with null always returns false, so use == null or is null to check for null.
int? hp = null;
// Applying arithmetic operators to null propagates null.
int? result = hp + 100; // result is null.
// Use the null-coalescing operator to provide a default value.
int actualHp = hp ?? 0; // If hp is null, use 0.
// Use == null or is null for null checks.
if (hp == null) {
// Code to run when hp is null.
}
Sample Code
ArithmeticOperators.cs
using System;
class ArithmeticOperators {
static void Main() {
int scoreA = 850; // Base score.
int bonus = 150; // Bonus points.
Console.WriteLine("=== Arithmetic Operators: Basics ===");
Console.WriteLine("Addition (+): " + (scoreA + bonus)); // 1000
Console.WriteLine("Subtraction (-): " + (scoreA - bonus)); // 700
Console.WriteLine("Multiplication (*): " + (scoreA * 2)); // 1700
Console.WriteLine("Remainder (%): " + (scoreA % 7)); // Remainder of 850 divided by 7.
// When dividing two integers, the decimal part is truncated.
Console.WriteLine("\n=== Integer Division ===");
int intDiv = scoreA / 4; // Integer division: 212 (no decimal).
double doubleDiv = (double)scoreA / 4; // Cast to double for floating-point division: 212.5.
Console.WriteLine("int / int: " + intDiv); // 212
Console.WriteLine("double / int: " + doubleDiv); // 212.5
// The difference appears when the result is not evenly divisible.
int intRemainder = 850 / 7; // 121 (0.42... is truncated).
double doubleRemainder = (double)850 / 7; // 121.4285...
Console.WriteLine("850 / 7 (int): " + intRemainder); // 121
Console.WriteLine("850 / 7 (double): " + doubleRemainder); // 121.42857142857143
Console.WriteLine("\n=== Increment / Decrement ===");
int level = 1;
Console.WriteLine("Initial level: " + level); // 1
level++; // Postfix increment: adds 1 after the expression is evaluated.
Console.WriteLine("After level++: " + level); // 2
++level; // Prefix increment: adds 1 before the expression is evaluated.
Console.WriteLine("After ++level: " + level); // 3
level--; // Postfix decrement: subtracts 1 after the expression is evaluated.
Console.WriteLine("After level--: " + level); // 2
// The difference between prefix and postfix matters inside an expression.
int a = 5;
int b = a++; // b gets the value before the increment (5); a becomes 6.
Console.WriteLine("a = " + a + ", b = " + b); // a=6, b=5
int c = 5;
int d = ++c; // d gets the value after the increment (6); c also becomes 6.
Console.WriteLine("c = " + c + ", d = " + d); // c=6, d=6
// When you join a number and a string with +, the number is automatically converted to a string.
Console.WriteLine("\n=== The + Operator with Strings ===");
string name = "item_a";
int score = 850;
string msg = name + "'s score is " + score + "."; // The number is converted to a string.
Console.WriteLine(msg); // item_a's score is 850.
Console.WriteLine("Total: " + (score + 150)); // Total: 1000 (numeric addition).
Console.WriteLine("Total: " + score + 150); // Total: 850150 (string concatenation).
}
}
This produces the following output:
dotnet script ArithmeticOperators.cs === Arithmetic Operators: Basics === Addition (+): 1000 Subtraction (-): 700 Multiplication (*): 1700 Remainder (%): 3 === Integer Division === int / int: 212 double / int: 212.5 850 / 7 (int): 121 850 / 7 (double): 121.42857142857143 === Increment / Decrement === Initial level: 1 After level++: 2 After ++level: 3 After level--: 2 a = 6, b = 5 c = 6, d = 6 === The + Operator with Strings === item_a's score is 850. Total: 1000 Total: 850150
ComparisonLogicalOperators.cs
using System;
class ComparisonLogicalOperators {
static void Main() {
int scoreA = 850; // Score A.
int scoreB = 720; // Score B.
int scoreC = 930; // Score C.
Console.WriteLine("=== Comparison Operators ===");
Console.WriteLine("scoreA == scoreB: " + (scoreA == scoreB)); // False
Console.WriteLine("scoreA != scoreB: " + (scoreA != scoreB)); // True
Console.WriteLine("scoreA > scoreB: " + (scoreA > scoreB)); // True
Console.WriteLine("scoreA >= scoreC: " + (scoreA >= scoreC)); // False
Console.WriteLine("scoreA < scoreC: " + (scoreA < scoreC)); // True
Console.WriteLine("scoreA <= scoreC: " + (scoreA <= scoreC)); // True
Console.WriteLine("\n=== String Comparison ===");
string name1 = "item_a";
string name2 = "item_a"; // A separate string variable with the same content.
string name3 = "item_b";
Console.WriteLine("name1 == name2: " + (name1 == name2)); // True (same content).
Console.WriteLine("name1 == name3: " + (name1 == name3)); // False (different content).
Console.WriteLine("\n=== Logical Operators ===");
bool isActive = true; // Whether the item is active.
bool hasBoost = true; // Whether the item has a boost.
bool hasOption = false; // Whether the item has an option enabled.
// && (AND): returns true only when both operands are true.
bool optionEnabled = isActive && hasOption;
Console.WriteLine("Option enabled (isActive && hasOption): " + optionEnabled); // False
// || (OR): returns true when at least one operand is true.
bool isStrong = isActive || hasBoost;
Console.WriteLine("Is effective (isActive || hasBoost): " + isStrong); // True
// ! (NOT): inverts the value.
bool isInactive = !isActive;
Console.WriteLine("Is inactive (!isActive): " + isInactive); // False
Console.WriteLine("\n=== Compound Conditions ===");
// Determines whether the item passes (compound condition example).
int threshold = 900; // Minimum score required to pass.
bool passes = isActive && (scoreA >= threshold || hasBoost);
Console.WriteLine("Passes (isActive && (score sufficient || hasBoost)): " + passes); // True
// &&: if the left operand is false, the right operand is not evaluated.
// ||: if the left operand is true, the right operand is not evaluated.
Console.WriteLine("\n=== Short-Circuit Evaluation ===");
// Safe member access pattern combined with a null check.
string targetName = "item_x";
bool hasLongName = targetName != null && targetName.Length > 3;
// If targetName is null, targetName.Length is never evaluated, so no NullReferenceException is thrown.
Console.WriteLine("Name is longer than 3 characters: " + hasLongName); // True (6 characters).
// When null, short-circuit evaluation skips the right operand.
string nullName = null;
bool isNullLong = nullName != null && nullName.Length > 3;
Console.WriteLine("null name is longer than 3 characters: " + isNullLong); // False (right side not evaluated).
}
}
This produces the following output:
dotnet script ComparisonLogicalOperators.cs === Comparison Operators === scoreA == scoreB: False scoreA != scoreB: True scoreA > scoreB: True scoreA >= scoreC: False scoreA < scoreC: True scoreA <= scoreC: True === String Comparison === name1 == name2: True name1 == name3: False === Logical Operators === Option enabled (isActive && hasOption): False Is effective (isActive || hasBoost): True Is inactive (!isActive): False === Compound Conditions === Passes (isActive && (score sufficient || hasBoost)): True === Short-Circuit Evaluation === Name is longer than 3 characters: True null name is longer than 3 characters: False
NullableOperators.cs
using System;
class NullableOperators {
static void Main() {
// int? is an integer type that can also hold null.
int? valueA = 1000; // Value A (has a value).
int? valueB = null; // Value B (unknown, so null).
Console.WriteLine("=== Nullable Types and Arithmetic Operators ===");
// Arithmetic operators on Nullable types propagate null.
int? resultA = valueA + 500; // Normal calculation when a value exists.
int? resultB = valueB + 500; // Arithmetic on null produces null.
Console.WriteLine("valueA + 500 = " + resultA); // 1500
Console.WriteLine("valueB + 500 = " + resultB); // (blank: null)
Console.WriteLine("\n=== Null-Coalescing Operator (??) ===");
int actualB = valueB ?? 0; // Use 0 if valueB is null.
Console.WriteLine("valueB ?? 0 = " + actualB); // 0
int actualA = valueA ?? 0; // valueA is not null, so it is used as-is.
Console.WriteLine("valueA ?? 0 = " + actualA); // 1000
Console.WriteLine("\n=== Nullable Type Comparisons ===");
// Use == null or is null to check for null.
Console.WriteLine("valueB == null: " + (valueB == null)); // True
Console.WriteLine("valueB is null: " + (valueB is null)); // True
Console.WriteLine("valueA == null: " + (valueA == null)); // False
// Comparisons such as > and < against null always return false.
Console.WriteLine("null > 0: " + (valueB > 0)); // False
Console.WriteLine("null < 0: " + (valueB < 0)); // False
Console.WriteLine("null == 0: " + (valueB == 0)); // False (null is not equal to 0).
Console.WriteLine("\n=== Bitwise Operators for Flag Management ===");
// Managing state flags with bits.
const int FLAG_A = 0b0001; // Flag A.
const int FLAG_B = 0b0010; // Flag B.
const int FLAG_C = 0b0100; // Flag C.
const int FLAG_D = 0b1000; // Flag D.
int flags = FLAG_A | FLAG_B | FLAG_C; // Set multiple flags with OR.
Console.WriteLine("Flags: " + flags); // 7 (0111)
bool hasA = (flags & FLAG_A) != 0;
bool hasD = (flags & FLAG_D) != 0;
Console.WriteLine("FLAG_A: " + hasA); // True
Console.WriteLine("FLAG_D: " + hasD); // False
// Add a flag using OR.
flags |= FLAG_D;
Console.WriteLine("Flags after adding FLAG_D: " + flags); // 15 (1111)
// Remove a flag using AND NOT.
flags &= ~FLAG_B;
Console.WriteLine("Flags after removing FLAG_B: " + flags); // 13 (1101)
}
}
This produces the following output:
dotnet script NullableOperators.cs === Nullable Types and Arithmetic Operators === valueA + 500 = 1500 valueB + 500 = === Null-Coalescing Operator (??) === valueB ?? 0 = 0 valueA ?? 0 = 1000 === Nullable Type Comparisons === valueB == null: True valueB is null: True valueA == null: False null > 0: False null < 0: False null == 0: False === Bitwise Operators for Flag Management === Flags: 7 FLAG_A: True FLAG_D: False Flags after adding FLAG_D: 15 Flags after removing FLAG_B: 13
Common Mistakes
Expecting a decimal from integer division
The result of int / int is int. The decimal part is truncated, so 9 / 2 produces 4, not 4.5. Cast at least one operand to double when you need a floating-point result.
int a = 9; int b = 2; int wrong = a / b; Console.WriteLine(wrong); // 4 (decimal part truncated) double correct = (double)a / b; Console.WriteLine(correct); // 4.5
String + number concatenation instead of addition
Writing "Result: " + a + b converts both a and b to strings before concatenating, so no numeric addition occurs. Wrap the addition in parentheses to add the numbers first.
int x = 100;
int y = 200;
Console.WriteLine("Total: " + x + y); // Total: 100200 (string concatenation)
Console.WriteLine("Total: " + (x + y)); // Total: 300 (numeric addition)
Summary
The most important thing to watch out for with C# operators is integer division. When both operands of / are int, the result is also an int and the decimal part is always truncated. 9 / 2 produces 4, not 4.5. If you need a floating-point result, cast explicitly using (double)a / b.
The + operator on string performs string concatenation. Writing Console.WriteLine("Total: " + a + b) performs string concatenation, not numeric addition. To add the numbers first, wrap them in parentheses: "Total: " + (a + b).
Taking advantage of short-circuit evaluation in && and || lets you safely access properties or methods after a null check. Note that & and | (bitwise operators) also work on bool, but they do not short-circuit, so use && and || for normal conditional logic.
Arithmetic operations on Nullable types (such as int?) propagate null. Either provide a default value with the null-coalescing operator (null ?? 0), or check for null using the HasValue property before performing the calculation. For more on Nullable types, see Nullable Types. For more on the null-coalescing operator, see Null-Coalescing Operator (??).
If you find any errors or copyright issues, please contact us.