Control Flow
Control flow statements determine the order in which code is executed. C# provides a rich set of control flow constructs for conditional execution, loops, and branching.
Conditional Statements
if, else if, else
The if statement executes a block of code if a specified condition is true.
int age = 25;
// Basic if statement
if (age >= 18)
{
Console.WriteLine("You are an adult.");
}
// if-else statement
if (age >= 18)
{
Console.WriteLine("You are an adult.");
}
else
{
Console.WriteLine("You are a minor.");
}
// if-else if-else chain
if (age < 13)
{
Console.WriteLine("Child");
}
else if (age < 18)
{
Console.WriteLine("Teenager");
}
else if (age < 65)
{
Console.WriteLine("Adult");
}
else
{
Console.WriteLine("Senior");
}
// Nested if statements
if (age >= 18)
{
if (age < 21)
{
Console.WriteLine("You are an adult, but cannot drink in the US.");
}
else
{
Console.WriteLine("You are an adult and can drink in the US.");
}
}
Conditional (Ternary) Operator
The conditional operator ?: is a shorthand for the if-else statement.
int age = 20;
// Ternary operator
string status = (age >= 18) ? "Adult" : "Minor";
// Equivalent if-else
string status2;
if (age >= 18)
{
status2 = "Adult";
}
else
{
status2 = "Minor";
}
// Nested ternary (use with caution for readability)
string category = (age < 13) ? "Child" :
(age < 18) ? "Teenager" :
(age < 65) ? "Adult" : "Senior";
// Ternary with expressions
int a = 5, b = 10;
int max = (a > b) ? a : b; // 10
switch Statement
The switch statement selects a statement list to execute based on a pattern match with a match expression.
int day = 3;
// Basic switch on a value
switch (day)
{
case 1:
Console.WriteLine("Monday");
break;
case 2:
Console.WriteLine("Tuesday");
break;
case 3:
Console.WriteLine("Wednesday");
break;
case 4:
Console.WriteLine("Thursday");
break;
case 5:
Console.WriteLine("Friday");
break;
case 6:
case 7:
Console.WriteLine("Weekend");
break;
default:
Console.WriteLine("Invalid day");
break;
}
// Switch with pattern matching (C# 7+)
object obj = "Hello";
switch (obj)
{
case int i:
Console.WriteLine($"Integer: {i}");
break;
case string s:
Console.WriteLine($"String: {s}");
break;
case bool b when b == true: // Case with a when condition
Console.WriteLine("True boolean");
break;
case null:
Console.WriteLine("Null value");
break;
default:
Console.WriteLine("Unknown type");
break;
}
// Switch with ranges (C# 9+)
int score = 85;
switch (score)
{
case >= 90:
Console.WriteLine("A");
break;
case >= 80:
Console.WriteLine("B");
break;
case >= 70:
Console.WriteLine("C");
break;
case >= 60:
Console.WriteLine("D");
break;
default:
Console.WriteLine("F");
break;
}
switch Expression (C# 8+)
The switch expression is a more concise form of the switch statement that returns a value.
int day = 3;
// Basic switch expression
string dayName = day switch
{
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 => "Thursday",
5 => "Friday",
6 or 7 => "Weekend",
_ => "Invalid day" // Default case
};
// Switch expression with pattern matching
object obj = "Hello";
string typeDescription = obj switch
{
int i => $"Integer: {i}",
string s => $"String: {s}",
bool b when b == true => "True boolean",
null => "Null value",
_ => "Unknown type"
};
// Switch expression with property patterns (C# 8+)
var person = new { Name = "John", Age = 25 };
string description = person switch
{
{ Name: "John", Age: 25 } => "John who is 25",
{ Name: "John" } => "John with any age",
{ Age: > 18 } => "Any adult",
_ => "Unknown person"
};
// Switch expression with tuple patterns (C# 8+)
var point = (X: 5, Y: -2);
string quadrant = point switch
{
(0, 0) => "Origin",
(_, 0) => "X-axis",
(0, _) => "Y-axis",
(> 0, > 0) => "First quadrant",
(< 0, > 0) => "Second quadrant",
(< 0, < 0) => "Third quadrant",
(> 0, < 0) => "Fourth quadrant",
_ => "Unknown"
};
Null-Coalescing Operator (??)
The null-coalescing operator ?? returns the left-hand operand if it isn't null; otherwise, it returns the right-hand operand.
string name = null;
string displayName = name ?? "Guest"; // "Guest"
// Chaining null-coalescing operators
string firstName = null;
string middleName = null;
string lastName = "Smith";
string fullName = firstName ?? middleName ?? lastName ?? "Unknown"; // "Smith"
Null-Coalescing Assignment Operator (??=) (C# 8+)
The null-coalescing assignment operator ??= assigns the right-hand operand to the left-hand operand only if the left-hand operand is null.
string name = null;
name ??= "Guest"; // name is now "Guest"
// Equivalent to:
// if (name == null)
// name = "Guest";
// Useful for lazy initialization
List<int> numbers = null;
(numbers ??= new List<int>()).Add(5); // Initialize and use in one line
Console.WriteLine(numbers.Count); // 1
Iteration Statements (Loops)
for Loop
The for loop executes a block of code repeatedly until a specified condition evaluates to false.
// Basic for loop
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i); // 0, 1, 2, 3, 4
}
// Multiple initialization and iterators
for (int i = 0, j = 10; i < j; i++, j--)
{
Console.WriteLine($"i = {i}, j = {j}"); // i=0,j=10; i=1,j=9; i=2,j=8; i=3,j=7; i=4,j=6
}
// Omitting parts of the for statement
int k = 0;
for (; k < 5;)
{
Console.WriteLine(k);
k++;
}
// Infinite loop (use with caution)
// for (;;)
// {
// // Code that runs forever (until break)
// }
// Nested for loops
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 2; j++)
{
Console.WriteLine($"({i},{j})");
}
}
foreach Loop
The foreach loop iterates over elements in a collection.
// foreach with array
string[] names = { "Alice", "Bob", "Charlie" };
foreach (string name in names)
{
Console.WriteLine(name);
}
// foreach with List
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
foreach (int number in numbers)
{
Console.WriteLine(number);
}
// foreach with Dictionary
Dictionary<string, int> ages = new Dictionary<string, int>
{
{ "Alice", 25 },
{ "Bob", 30 },
{ "Charlie", 35 }
};
foreach (KeyValuePair<string, int> pair in ages)
{
Console.WriteLine($"{pair.Key} is {pair.Value} years old");
}
// Using deconstruction with foreach (C# 7+)
foreach (var (name, age) in ages)
{
Console.WriteLine($"{name} is {age} years old");
}
// foreach with custom types that implement IEnumerable
foreach (var item in myCustomCollection)
{
// Process item
}
while Loop
The while loop executes a block of code as long as a specified condition is true.
// Basic while loop
int i = 0;
while (i < 5)
{
Console.WriteLine(i); // 0, 1, 2, 3, 4
i++;
}
// while with complex condition
string input = "";
while (input != "exit" && input != "quit")
{
Console.Write("Enter command (type 'exit' to quit): ");
input = Console.ReadLine().ToLower();
// Process input
}
// Infinite loop with break
int j = 0;
while (true)
{
Console.WriteLine(j);
j++;
if (j >= 5)
break;
}
do-while Loop
The do-while loop is similar to the while loop, except the condition is evaluated after the loop body executes, ensuring the loop body runs at least once.
// Basic do-while loop
int i = 0;
do
{
Console.WriteLine(i); // 0, 1, 2, 3, 4
i++;
} while (i < 5);
// do-while for input validation
string password;
do
{
Console.Write("Enter a password (at least 6 characters): ");
password = Console.ReadLine();
} while (password.Length < 6);
// do-while that always executes at least once
int j = 10;
do
{
Console.WriteLine("This runs once even though the condition is false");
} while (j < 5);
Jump Statements
break Statement
The break statement terminates the closest enclosing loop or switch statement.
// break in a for loop
for (int i = 0; i < 10; i++)
{
if (i == 5)
break; // Exit the loop when i is 5
Console.WriteLine(i); // 0, 1, 2, 3, 4
}
// break in a while loop
int j = 0;
while (j < 10)
{
if (j == 5)
break; // Exit the loop when j is 5
Console.WriteLine(j); // 0, 1, 2, 3, 4
j++;
}
// break in a nested loop (only breaks out of the innermost loop)
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i == 1 && j == 1)
break; // Breaks out of the inner loop only
Console.WriteLine($"({i},{j})");
}
}
// break in a switch statement
int day = 3;
switch (day)
{
case 1:
Console.WriteLine("Monday");
break; // Exit the switch
case 2:
Console.WriteLine("Tuesday");
break; // Exit the switch
default:
Console.WriteLine("Other day");
break; // Exit the switch
}
continue Statement
The continue statement skips the rest of the current iteration and proceeds to the next iteration of the loop.
// continue in a for loop
for (int i = 0; i < 10; i++)
{
if (i % 2 == 0)
continue; // Skip even numbers
Console.WriteLine(i); // 1, 3, 5, 7, 9
}
// continue in a while loop
int j = 0;
while (j < 10)
{
j++;
if (j % 2 == 0)
continue; // Skip even numbers
Console.WriteLine(j); // 1, 3, 5, 7, 9
}
// continue in a nested loop (only affects the innermost loop)
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (j == 1)
continue; // Skip when j is 1
Console.WriteLine($"({i},{j})"); // (0,0), (0,2), (1,0), (1,2), (2,0), (2,2)
}
}
return Statement
The return statement exits the current method and optionally returns a value.
// return with a value
int Add(int a, int b)
{
return a + b; // Return the sum and exit the method
}
// Early return for validation
bool IsValidUsername(string username)
{
if (string.IsNullOrEmpty(username))
return false; // Invalid: empty username
if (username.Length < 3)
return false; // Invalid: too short
if (username.Length > 20)
return false; // Invalid: too long
// More validation...
return true; // Valid username
}
// return without a value (in a void method)
void ProcessData(int[] data)
{
if (data == null || data.Length == 0)
return; // Exit the method early
// Process the data...
}
goto Statement
The goto statement transfers control to a labeled statement. While generally discouraged in modern programming, it can be useful in specific scenarios.
// goto with a label
int i = 0;
start: // Label
Console.WriteLine(i);
i++;
if (i < 5)
goto start; // Go back to the 'start' label
// goto in a nested loop (can break out of multiple loops)
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i == 1 && j == 1)
goto exit; // Exit both loops
Console.WriteLine($"({i},{j})");
}
}
exit: // Label
Console.WriteLine("Exited loops");
// goto in a switch statement
int option = 2;
switch (option)
{
case 1:
Console.WriteLine("Option 1");
goto case 3; // Execute case 3 after this
case 2:
Console.WriteLine("Option 2");
goto case 1; // Execute case 1 after this
case 3:
Console.WriteLine("Option 3");
break;
default:
Console.WriteLine("Default option");
break;
}
// Output: "Option 2", "Option 1", "Option 3"
Exception Handling
try-catch-finally
The try-catch-finally statement handles exceptions that might occur during execution.
// Basic try-catch
try
{
int result = 10 / 0; // This will throw a DivideByZeroException
Console.WriteLine(result); // This line won't execute
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Error: {ex.Message}"); // "Error: Attempted to divide by zero."
}
// Multiple catch blocks
try
{
string text = null;
int length = text.Length; // This will throw a NullReferenceException
}
catch (NullReferenceException ex)
{
Console.WriteLine("Null reference error: " + ex.Message);
}
catch (ArgumentException ex)
{
Console.WriteLine("Argument error: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("General error: " + ex.Message); // Catches any other exception
}
// try-catch-finally
try
{
File.ReadAllText("nonexistent.txt"); // This will throw a FileNotFoundException
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"File not found: {ex.Message}");
}
finally
{
Console.WriteLine("This always executes, whether an exception occurred or not");
// Clean up resources, close files, etc.
}
// Nested try-catch
try
{
try
{
int result = 10 / 0;
}
catch (ArithmeticException ex)
{
Console.WriteLine("Inner catch: " + ex.Message);
throw; // Re-throw the exception to the outer catch
}
}
catch (Exception ex)
{
Console.WriteLine("Outer catch: " + ex.Message);
}
throw Statement
The throw statement throws an exception.
// Throwing a predefined exception
void ProcessAge(int age)
{
if (age < 0)
throw new ArgumentException("Age cannot be negative");
// Process age...
}
// Re-throwing an exception
try
{
int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Logging the error: " + ex.Message);
throw; // Re-throw the same exception
}
// Throwing a new exception with the original as inner exception
try
{
File.ReadAllText("config.txt");
}
catch (FileNotFoundException ex)
{
throw new ApplicationException("Configuration error", ex);
}
// Creating and throwing a custom exception
public class CustomException : Exception
{
public CustomException(string message) : base(message) { }
public CustomException(string message, Exception inner) : base(message, inner) { }
}
void ProcessData(string data)
{
if (string.IsNullOrEmpty(data))
throw new CustomException("Data cannot be empty");
}
Exception Filters (C# 6+)
Exception filters allow you to specify a condition for a catch block.
// Basic exception filter
try
{
ProcessFile("data.txt");
}
catch (IOException ex) when (ex.HResult == -2147024816) // File not found
{
Console.WriteLine("The file was not found");
}
catch (IOException ex) when (ex.HResult == -2147024864) // Path not found
{
Console.WriteLine("The path was not found");
}
catch (IOException ex) // Any other IO exception
{
Console.WriteLine("IO error: " + ex.Message);
}
// Exception filter with logging (without handling)
try
{
// Some code that might throw
}
catch (Exception ex) when (LogException(ex))
{
// This block never executes because LogException returns false
}
bool LogException(Exception ex)
{
Console.WriteLine($"Exception logged: {ex.Message}");
return false; // Don't handle the exception, let it propagate
}
// Exception filter with complex conditions
try
{
// Some code that might throw
}
catch (Exception ex) when (ex.Message.Contains("specific error") && DateTime.Now.DayOfWeek == DayOfWeek.Monday)
{
Console.WriteLine("Specific error occurred on Monday");
}
Pattern Matching (C# 7+)
Pattern matching allows you to test if an expression matches a certain pattern and extract information from it.
Type Patterns
// Type pattern with 'is'
object obj = "Hello";
if (obj is string s)
{
Console.WriteLine($"String of length {s.Length}"); // Uses the extracted variable 's'
}
// Type pattern with 'switch'
switch (obj)
{
case int i:
Console.WriteLine($"Integer: {i}");
break;
case string s:
Console.WriteLine($"String: {s}");
break;
case bool b:
Console.WriteLine($"Boolean: {b}");
break;
case null:
Console.WriteLine("Null value");
break;
default:
Console.WriteLine("Unknown type");
break;
}
Constant Patterns
// Constant pattern with 'is'
object value = 42;
if (value is 42)
{
Console.WriteLine("Value is 42");
}
// Constant pattern with 'switch'
switch (value)
{
case 0:
Console.WriteLine("Zero");
break;
case 42:
Console.WriteLine("The answer");
break;
case null:
Console.WriteLine("Null");
break;
default:
Console.WriteLine("Something else");
break;
}
Relational Patterns (C# 9+)
// Relational patterns with 'is'
int age = 25;
if (age is >= 18 and < 65)
{
Console.WriteLine("Working age adult");
}
// Relational patterns with 'switch'
string description = age switch
{
< 0 => "Invalid age",
0 => "Newborn",
< 13 => "Child",
< 18 => "Teenager",
< 65 => "Adult",
_ => "Senior"
};
Logical Patterns (C# 9+)
// 'and', 'or', and 'not' patterns
int value = 7;
if (value is > 0 and < 10 and not 5)
{
Console.WriteLine("Single positive digit, not 5");
}
// Combined with other patterns
object obj = "Hello";
if (obj is string { Length: > 3 and < 10 })
{
Console.WriteLine("String with length between 4 and 9");
}
Property Patterns (C# 8+)
// Simple property pattern
var person = new { Name = "John", Age = 25 };
if (person is { Age: >= 18 })
{
Console.WriteLine("Adult");
}
// Nested property pattern
var order = new { Customer = new { Name = "Alice", IsVip = true }, Total = 100.0 };
if (order is { Customer: { IsVip: true }, Total: >= 50.0 })
{
Console.WriteLine("VIP order with high value");
}
// Property pattern with switch expression
string description = person switch
{
{ Name: "John", Age: 25 } => "John who is 25",
{ Name: "John" } => "John with any age",
{ Age: >= 18 } => "Any adult",
_ => "Unknown person"
};
Tuple Patterns (C# 8+)
// Tuple pattern with switch expression
var point = (X: 5, Y: -2);
string quadrant = point switch
{
(0, 0) => "Origin",
(_, 0) => "X-axis",
(0, _) => "Y-axis",
(> 0, > 0) => "First quadrant",
(< 0, > 0) => "Second quadrant",
(< 0, < 0) => "Third quadrant",
(> 0, < 0) => "Fourth quadrant",
_ => "Unknown"
};
// Tuple pattern with method parameters
string Classify(int x, int y) => (x, y) switch
{
(0, 0) => "Origin",
var (a, b) when a > 0 && b > 0 => "First quadrant",
var (a, b) when a < 0 && b > 0 => "Second quadrant",
var (a, b) when a < 0 && b < 0 => "Third quadrant",
var (a, b) when a > 0 && b < 0 => "Fourth quadrant",
_ => "On an axis"
};
List Patterns (C# 11+)
// List pattern with 'is'
int[] numbers = { 1, 2, 3 };
if (numbers is [1, 2, 3])
{
Console.WriteLine("Exact match: [1, 2, 3]");
}
// List pattern with discard
if (numbers is [1, _, 3])
{
Console.WriteLine("First element is 1, last is 3");
}
// List pattern with 'var'
if (numbers is [var first, _, var last])
{
Console.WriteLine($"First: {first}, Last: {last}");
}
// List pattern with slice pattern
if (numbers is [1, .. var middle, 3])
{
Console.WriteLine($"Middle: {string.Join(", ", middle)}"); // "2"
}
// List pattern with switch expression
string description = numbers switch
{
[] => "Empty array",
[var single] => $"Single element: {single}",
[var first, var second] => $"Two elements: {first}, {second}",
[1, 2, 3] => "Exactly [1, 2, 3]",
[1, ..] => "Starts with 1",
[.., 3] => "Ends with 3",
_ => "Other array"
};