C# Operators — Arithmetic, Relational, Logical, Bitwise, and Null-Conditional
In this tutorial, you will learn about C# Operators. We cover key concepts, practical examples, and best practices to help you master this topic.
C# operators are special symbols that perform operations on operands, covering arithmetic calculations, value comparisons, boolean logic, bit manipulation, and null-safe access with a well-defined precedence hierarchy.
What You'll Learn
You will master all categories of C# operators: arithmetic operators for mathematical calculations, relational operators for comparison, logical operators for boolean expressions, bitwise operators for low-level bit manipulation, null-conditional operators for safe member access, and compound assignment operators. You will also understand operator precedence and associativity.
Why It Matters
Operators are the building blocks of every expression in C#. Misunderstanding operator precedence leads to subtle bugs. The null-conditional operator (?.) is one of the most important features for writing null-safe code in modern C#. Bitwise operators are essential for working with flags, permissions, encryption, and low-level protocols.
Real-World Use
Security applications use bitwise operators for permission flags: FileAccess.Read | FileAccess.Write. Financial applications use compound assignment for running totals. ASP.NET Core uses null-conditional operators extensively in request handling pipelines to avoid null reference exceptions. Game developers use bitwise operations for collision masks and state management.
Learning Path
graph LR
A["05: Built-in Types"] --> B["06: Operators"]
B --> C["07: Control Flow"]
C --> D["08: Loops"]
D --> E["09: Classes"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
style E fill:#4a90d9,stroke:#2c5f8a,color:#fff
Arithmetic Operators
int a = 10, b = 3;
Console.WriteLine($"a + b = {a + b}"); // 13
Console.WriteLine($"a - b = {a - b}"); // 7
Console.WriteLine($"a * b = {a * b}"); // 30
Console.WriteLine($"a / b = {a / b}"); // 3 (integer division)
Console.WriteLine($"a % b = {a % b}"); // 1 (modulus)
Console.WriteLine($"a++ = {a++}"); // 10 (post-increment), a is now 11
Console.WriteLine($"++a = {++a}"); // 12 (pre-increment)
Console.WriteLine($"a-- = {a--}"); // 12 (post-decrement), a is now 11
For floating-point results, ensure at least one operand is floating-point:
Console.WriteLine(10 / 3.0); // 3.3333333333333335
Console.WriteLine(10.0 / 3); // 3.3333333333333335
Relational Operators
int x = 5, y = 10;
Console.WriteLine($"x < y: {x < y}"); // True
Console.WriteLine($"x > y: {x > y}"); // False
Console.WriteLine($"x <= y: {x <= y}"); // True
Console.WriteLine($"x >= y: {x >= y}"); // False
Console.WriteLine($"x == y: {x == y}"); // False
Console.WriteLine($"x != y: {x != y}"); // True
// Reference equality vs value equality
string s1 = "Hello";
string s2 = "Hello";
Console.WriteLine(s1 == s2); // True (string overloads == for value equality)
Console.WriteLine((object)s1 == (object)s2); // True (interned, but use ReferenceEquals)
Logical Operators
bool a = true, b = false;
Console.WriteLine($"a && b: {a && b}"); // False (AND - short-circuit)
Console.WriteLine($"a || b: {a || b}"); // True (OR - short-circuit)
Console.WriteLine($"!a: {!a}"); // False (NOT)
Console.WriteLine($"a ^ b: {a ^ b}"); // True (XOR)
// Short-circuit behavior
bool CheckFirst() { Console.WriteLine("CheckFirst called"); return true; }
bool CheckSecond() { Console.WriteLine("CheckSecond called"); return false; }
bool result = CheckFirst() || CheckSecond(); // CheckSecond never called!
Bitwise Operators
uint flags1 = 0b1100; // 12
uint flags2 = 0b1010; // 10
Console.WriteLine($"flags1 & flags2: {Convert.ToString(flags1 & flags2, 2)}"); // 1000 (AND)
Console.WriteLine($"flags1 | flags2: {Convert.ToString(flags1 | flags2, 2)}"); // 1110 (OR)
Console.WriteLine($"flags1 ^ flags2: {Convert.ToString(flags1 ^ flags2, 2)}"); // 0110 (XOR)
Console.WriteLine($"~flags1: {Convert.ToString(~flags1, 2)}"); // ...11110011 (NOT)
Console.WriteLine($"flags1 << 2: {Convert.ToString(flags1 << 2, 2)}"); // 110000 (left shift)
Console.WriteLine($"flags1 >> 2: {Convert.ToString(flags1 >> 2, 2)}"); // 11 (right shift)
Bitwise operators are commonly used for enum flags:
[Flags]
enum FilePermissions
{
None = 0,
Read = 1,
Write = 2,
Execute = 4,
All = Read | Write | Execute
}
FilePermissions perms = FilePermissions.Read | FilePermissions.Write;
Console.WriteLine(perms); // Read, Write
Console.WriteLine(perms.HasFlag(FilePermissions.Read)); // True
Console.WriteLine(perms.HasFlag(FilePermissions.Execute)); // False
Null-Conditional Operators
The null-conditional operator (?.) is one of the most valuable features in modern C#:
// Instead of:
if (person != null && person.Address != null)
{
Console.WriteLine(person.Address.Street);
}
// Use:
Console.WriteLine(person?.Address?.Street);
// Null-coalescing operator
string name = person?.Name ?? "Unknown";
Console.WriteLine(name);
// Null-coalescing assignment (C# 8+)
List<int> numbers = null;
numbers ??= new List<int>();
numbers.Add(42); // Safe because numbers is now initialized
Conditional Access with Indexers
int[] arr = null;
int? value = arr?[0]; // null, no exception
Console.WriteLine(value); // (blank)
arr = new[] { 10, 20, 30 };
Console.WriteLine(arr?[1]); // 20
Compound Assignment Operators
int n = 10;
n += 5; // n = n + 5; => 15
n -= 3; // n = n - 3; => 12
n *= 2; // n = n * 2; => 24
n /= 4; // n = n / 4; => 6
n %= 3; // n = n % 3; => 0
// Bitwise compound
uint bits = 0b1010;
bits |= 0b0101; // 0b1111
bits &= 0b1100; // 0b1100
bits ^= 0b0011; // 0b1111
bits <<= 2; // 0b111100
Operator Precedence
From highest to lowest precedence:
| Level | Operators | Category |
|-------|-----------|----------|
| 1 | x.y, f(x), a[x], x++, x--, new, typeof, checked, unchecked | Primary |
| 2 | +x, -x, !x, ~x, ++x, --x, (T)x | Unary |
| 3 | x * y, x / y, x % y | Multiplicative |
| 4 | x + y, x - y | Additive |
| 5 | x << y, x >> y | Shift |
| 6 | x < y, x > y, x <= y, x >= y, is, as | Relational |
| 7 | x == y, x != y | Equality |
| 8 | x & y | Bitwise AND |
| 9 | x ^ y | Bitwise XOR |
| 10 | x | y | Bitwise OR |
| 11 | x && y | Logical AND |
| 12 | x || y | Logical OR |
| 13 | x ?? y | Null-coalescing |
| 14 | x ? y : z | Ternary |
| 15 | x = y, x += y, etc. | Assignment |
Common Mistakes
Mistake 1: Confusing = (Assignment) with == (Equality)
if (x = 5) assigns 5 to x and the result (5) is truthy in C#. This compiles in some contexts but is almost always a bug. Use if (x == 5).
Mistake 2: Integer Division Surprise
double result = 1 / 2; gives 0.0 because both operands are integers. Use 1.0 / 2 or 1 / 2.0.
Mistake 3: Assuming && and & Are the Same
&& short-circuits (stops evaluating if the first operand is false). & always evaluates both operands. Use && for conditional logic, & for bitwise operations.
Mistake 4: Not Using Null-Conditional Operators
Old-style null checks lead to deeply nested if statements. Use ?. and ?? for cleaner, safer code.
Mistake 5: Forgetting Operator Precedence
if (x & y == 0) evaluates as x & (y == 0), not (x & y) == 0. Always use parentheses when mixing bitwise and relational operators.
Mistake 6: Misusing the Ternary Operator
var result = condition ? valueIfTrue : valueIfFalse; Both branches must have the same type or implicitly convertible types.
Practice Questions
- What is the difference between
&&and&operators? - What does the null-conditional operator
?.return when the object is null? - Explain the difference between
x++and++x. - How would you use the null-coalescing operator to provide a default value?
- What is the result of
(4 | 2) & 3?
Challenge
Write a program that uses [Flags] enum for user permissions (Read, Write, Delete, Admin). Use bitwise operators to combine permissions, check if a user has specific permissions, and toggle individual permissions on and off.
FAQ
Mini Project
Create a permission management system:
[Flags]
enum Permission
{
None = 0,
Read = 1,
Write = 2,
Execute = 4,
All = Read | Write | Execute
}
class UserPermissions
{
public string User { get; set; }
public Permission Permissions { get; set; }
public bool HasPermission(Permission p) => Permissions.HasFlag(p);
public void Grant(Permission p) => Permissions |= p;
public void Revoke(Permission p) => Permissions &= ~p;
public void Toggle(Permission p) => Permissions ^= p;
}
var user = new UserPermissions { User = "Alice" };
user.Grant(Permission.Read | Permission.Write);
Console.WriteLine($"Alice has Read: {user.HasPermission(Permission.Read)}");
Console.WriteLine($"Alice has Execute: {user.HasPermission(Permission.Execute)}");
user.Toggle(Permission.Execute);
Console.WriteLine($"Alice has Execute after toggle: {user.HasPermission(Permission.Execute)}");
user.Revoke(Permission.Read);
Console.WriteLine($"Alice has Read after revoke: {user.HasPermission(Permission.Read)}");
Console.WriteLine($"Alice permissions: {user.Permissions}");
Expected output:
Alice has Read: True
Alice has Execute: False
Alice has Execute after toggle: True
Alice has Read after revoke: False
Alice permissions: Write, Execute
What's Next
You have mastered all categories of C# operators. The next lesson covers control flow with if/else, switch statements, switch expressions, and pattern matching.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro