Backend Development with .NET
Session 04
C# Methods, Strings
& Exception Handling
Eng. Seif Mansour  ·  Andalusia Academy
Week 2  ·  2 hours
Session Goals

By the end of this session, you will be able to:

  • Define and call methods with various parameter and return types
  • Manipulate strings using built-in methods and StringBuilder
  • Handle null safely using nullable types and null operators
  • Catch and throw exceptions, and create custom exception types
Agenda
TimeSegmentTypeDuration
0:00Methods: definition & overloadingTheory + Demo25 min
0:25Strings & StringBuilderTheory + Demo20 min
0:45Null & nullable typesTheory + Demo15 min
1:00Break5 min
1:05Exception handlingTheory + Demo25 min
1:30ExercisesExercise30 min
Methods
Defining reusable blocks of logic, controlling parameters, and overloading for flexible APIs.
In this section
  • Method structure & return types
  • Expression-bodied methods
  • Overloading
  • Optional & named parameters
  • ref and out parameters
Method Structure
  • A method is a named block of code that performs a task and optionally returns a value
  • Signature: modifier returnType Name(params)
  • void methods perform an action and return nothing
  • Expression-bodied syntax (=>) for single-expression methods
static int Add(int a, int b)
{
    return a + b;
}

int result = Add(3, 4);   // 7

// void — no return value
static void PrintLine(string message)
{
    Console.WriteLine(message);
}

// Expression-bodied shorthand
static int Square(int n) => n * n;
static void Greet(string name)
    => Console.WriteLine($"Hello, {name}!");
Method Overloading
  • Same method name, different parameter lists
  • The compiler picks the right version based on argument types
  • Return type alone is not enough to distinguish overloads
  • Useful when the same operation applies to different data types
Note
You already use overloading every time you call Console.WriteLine — it has overloads for string, int, object, and more.
static int Add(int a, int b)
    => a + b;

static double Add(double a, double b)
    => a + b;

static int Add(int a, int b, int c)
    => a + b + c;

// Compiler picks the correct version:
Add(1, 2);          // int overload
Add(1.5, 2.5);      // double overload
Add(1, 2, 3);       // three-arg overload
Optional & Named Parameters
  • Optional parameters have a default value — callers may omit them
  • Optional parameters must come after required ones
  • Named arguments let you skip an optional and set a later one by name
  • Named arguments also improve readability at call sites
static void CreateUser(
    string name,
    bool isAdmin = false,
    int age = 18)
{
    Console.WriteLine(
        $"{name}, admin: {isAdmin}, age: {age}");
}

CreateUser("Alice");
// isAdmin=false, age=18

CreateUser("Bob", isAdmin: true);
// skip age, set isAdmin by name

CreateUser("Carol", age: 25);
// skip isAdmin, set age by name
ref and out Parameters
pass-by-reference
// ref: variable must be initialized before passing
static void Double(ref int value) => value *= 2;

int x = 5;
Double(ref x);   // x is now 10

// out: method is responsible for assigning the value
static bool TryDivide(int a, int b, out double result)
{
    if (b == 0) { result = 0; return false; }
    result = (double)a / b;
    return true;
}

if (TryDivide(10, 2, out double quotient))
    Console.WriteLine(quotient);   // 5
Why this matters
out is exactly how int.TryParse(s, out int value) works — now you know why the syntax looks that way.
Strings & StringBuilder
Working with text — why strings are immutable and when to reach for StringBuilder.
In this section
  • String immutability
  • Common string methods
  • Case-insensitive comparison
  • StringBuilder for loops
String Immutability & Common Methods
  • Strings in C# are immutable — every operation creates a new string
  • Methods like Trim() return a new value — assign it back or it is lost
  • Use string.Join to combine collections into a single string
Common mistake
s.Trim() does nothing if you don't assign the result: s = s.Trim();
string s = "  Hello, World!  ";

s.Length                    // 17
s.Trim()                    // "Hello, World!"
s.ToUpper()                 // "  HELLO, WORLD!  "
s.Contains("World")         // true
s.StartsWith("  Hello")     // true
s.Replace("World", "C#")    // "  Hello, C#!  "
s.Substring(7, 5)           // "World"
s.IndexOf("World")          // 9
s.Split(',')                // ["  Hello", " World!  "]

string.Join(", ",
    new[] { "a", "b", "c" }) // "a, b, c"
Comparison & StringBuilder

Case-insensitive comparison

string a = "hello";
string b = "HELLO";

bool eq = a == b;  // false

bool eqIgnore = string.Equals(
    a, b,
    StringComparison.OrdinalIgnoreCase);
// true
Best practice
Prefer OrdinalIgnoreCase for identifiers and API parameters; use CurrentCultureIgnoreCase only for user-facing text.

StringBuilder — avoid string thrashing in loops

var sb = new StringBuilder();

for (int i = 1; i <= 100; i++)
{
    sb.Append(i);
    if (i < 100) sb.Append(", ");
}

string result = sb.ToString();
// "1, 2, 3, ..., 100"

Each + on a string allocates a new object. StringBuilder mutates a buffer internally — use it whenever building strings in a loop.

Null & Nullable Types
Handling the absence of a value safely — without NullReferenceException.
In this section
  • Nullable value types (int?)
  • Null-coalescing operator ??
  • Null-conditional operator ?.
  • Chaining both operators
Nullable Types & Null Operators
  • Reference types (string, objects) can be null by default
  • Value types (int, bool) cannot — add ? to allow null
  • ?? provides a fallback when the left side is null
  • ?. accesses a member only if not null — returns null instead of throwing
// Nullable value type
int? age = null;
bool? isVerified = null;

// ?? — null-coalescing
int? userAge = null;
int display = userAge ?? 0;  // 0

// ?. — null-conditional
string? name = null;
int? length = name?.Length;      // null
string? upper = name?.ToUpper(); // null

// Chain both
int len = user?.Address?.PostalCode?.Length ?? 0;
Null Check Patterns
  • Always check before dereferencing a nullable reference
  • C# 9+ pattern is not null is preferred — it reads as plain English and is pattern-match aware
  • Inside an if (name != null) block, the compiler knows name is non-null (flow analysis)
Tip
Enable <Nullable>enable</Nullable> in your project file to get compiler warnings whenever you dereference a potentially-null reference.
string? name = GetName();

// Classic null check
if (name != null)
{
    Console.WriteLine(name.Length);
}

// C# 9+ pattern syntax (preferred)
if (name is not null)
{
    Console.WriteLine(name.Length);
}

// Inline guard with ??
string display = name ?? "Unknown";
Exception Handling
Catching, throwing, and designing custom exceptions for predictable failure paths.
In this section
  • try / catch / finally
  • Catch ordering — specific before general
  • Throwing exceptions
  • Re-throwing with bare throw
  • Custom exception types
try / catch / finally
exception-handling
try
{
    int result = 10 / int.Parse(Console.ReadLine());
    Console.WriteLine(result);
}
catch (DivideByZeroException)
{
    Console.WriteLine("Cannot divide by zero.");
}
catch (FormatException ex)
{
    Console.WriteLine($"Invalid input: {ex.Message}");
}
finally
{
    // Always runs — use to release resources
    Console.WriteLine("Done.");
}

finally runs whether an exception was thrown or not — ideal for cleanup (closing files, releasing locks).

Catch Ordering & Throwing
  • Put specific exception types before general ones — the first matching catch wins
  • A catch for Exception at the top swallows everything, including bugs you want to see
  • Use throw new ... to signal a precondition violation
  • nameof(b) keeps the parameter name refactor-safe
// Correct catch order
catch (FormatException ex)      // specific
catch (OverflowException ex)    // specific
catch (Exception ex)            // general — last!

// Throwing with a meaningful message
static int Divide(int a, int b)
{
    if (b == 0)
        throw new ArgumentException(
            "Divisor cannot be zero.",
            nameof(b));
    return a / b;
}
Re-throwing Exceptions
  • Sometimes you want to log and then let the exception propagate
  • Use bare throw; — it preserves the original stack trace
  • throw ex; resets the stack trace, making debugging much harder
Never do this
throw ex; — always use bare throw; to re-throw.
try
{
    // ... operation that may throw
}
catch (Exception ex)
{
    Console.WriteLine(
        $"Logging: {ex.Message}");

    throw;   // preserves stack trace
    // NOT: throw ex;
}
Custom Exception Types
Exceptions/InsufficientFundsException.cs
public class InsufficientFundsException : Exception
{
    public decimal Amount { get; }

    public InsufficientFundsException(decimal amount)
        : base($"Insufficient funds. Attempted to withdraw {amount:C}.")
    {
        Amount = amount;
    }
}

// Throwing
throw new InsufficientFundsException(500m);

// Catching
catch (InsufficientFundsException ex)
{
    Console.WriteLine($"Failed: {ex.Message}");
    Console.WriteLine($"Amount: {ex.Amount}");
}

Custom exceptions become essential in Session 10 — every API error will map to a specific exception type.

Key Concept
"Use bare throw; to re-throw — never throw ex;. The stack trace is your only map to the crime scene."
— Session 04
Summary
  • Methods encapsulate reusable logic; overloading lets the same name serve different types
  • Optional and named parameters reduce overload clutter and improve call-site clarity
  • Strings are immutable — always capture the result of string methods; use StringBuilder in loops
  • ?? provides a fallback; ?. short-circuits null dereferences safely
  • Catch specific exceptions first; bare throw; preserves the stack trace
  • Custom exception types carry domain-specific data and will become the backbone of API error design
What's Next

Next session: Session 05 — OOP I: Classes, Encapsulation & Inheritance

  • Defining classes with fields, properties, and constructors
  • Access modifiers and encapsulation
  • Inheritance and the base keyword
  • Method overriding with virtual and override
Before next session
Complete the four exercises at the end of today's session. Pay particular attention to exercise 4 — it combines exception handling with a real input loop.
Assignment

Build a safe calculator console app that uses methods, strings, nullable types, and exception handling.

  • Create a method double? SafeCalculate(string expression) that parses a string like "10 / 2" and returns the result, or null if the operation is invalid
  • Support operators: +, -, *, /
  • Throw a custom exception InvalidExpressionException (with the original input as a property) when the format is unrecognized
  • In Main, loop until the user types "exit", printing each result or an error message — never let an uncaught exception crash the app
  • Use StringBuilder to produce a summary of all calculations at the end
Bonus
Add a history command that prints all previous calculations using your StringBuilder summary at any point during the session, not just on exit.
Questions?
Session 04  ·  C# Methods, Strings & Exception Handling