Skip to content

C# Operators — Arithmetic, Relational, Logical, Bitwise, and Null-Conditional

DodaTech Updated 2026-06-28 8 min read

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

  1. What is the difference between && and & operators?
  2. What does the null-conditional operator ?. return when the object is null?
  3. Explain the difference between x++ and ++x.
  4. How would you use the null-coalescing operator to provide a default value?
  5. 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

What is the difference between `??` and `?.`?

?. (null-conditional) safely accesses members on a possibly-null target, returning null if the target is null. ?? (null-coalescing) provides a default value when the left operand is null.

Can I overload operators in my own types?

Yes. C# allows operator overloading for classes and structs. Common overloads include +, -, ==, !=, <, >, and implicit/explicit conversion operators.

What is the difference between `is` and `as` operators?

is checks if an object is compatible with a type (returns bool). as attempts a cast and returns null if it fails. as only works with reference types and nullable value types.

Why does C# not have a `**` power operator?

C# does not include a power operator by design. Use Math.Pow(x, y) for exponentiation. The language designers prioritize clarity over brevity for less common operations.

What is the `!` (null-forgiving) operator?

The ! operator (postfix) tells the compiler to suppress nullable warnings: string s = mightBeNull!;. It does not check for null at runtime; it only affects the compiler's static analysis.

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