C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
40: Custom Exceptions
Why can't I just use a generic Exception?
I get this question a lot. Technically, you can. Your code will compile, and the program will crash (or be caught) just fine. But here is the problem: when you're writing a try-catch block, you want to be precise. If you catch Exception, you're catching everything—including null reference errors or out-of-memory crashes that you probably shouldn't be silencing.
Imagine we're building a banking app. If a user tries to withdraw more money than they have, that's a business logic error, not a system failure. By creating a InsufficientFundsException, you can catch that specific error and show a friendly message like "Please top up your account," while still letting a SqlException bubble up to your logging system because that's a real technical problem that needs a dev's attention.
How do I actually build one?
It's simpler than you might think. You just inherit from the Exception class. Now, the "pro" way to do this is to provide a few different constructors. This ensures your custom exception behaves like the built-in ones, allowing you to pass a custom message or wrap another exception (the inner exception).
Here is how I usually set them up:
public class InsufficientFundsException : Exception
{
// Default constructor
public InsufficientFundsException() { }
// Constructor that takes a message
public InsufficientFundsException(string message)
: base(message) { }
// Constructor that takes a message and a previous exception
public InsufficientFundsException(string message, Exception inner)
: base(message, inner) { }
}
Notice the : base(message) part? That just passes the string up to the parent Exception class so that the standard .Message property works automatically. Also, always end your class name with "Exception"—it's the industry standard, and it makes your code searchable for other engineers.
Can I add my own data to the exception?
Yes, and this is where custom exceptions actually become powerful. You aren't limited to just a string message. You can add properties to the exception class that provide the "why" and "how" of the failure. This prevents you from having to parse a string message to figure out what went wrong.
Let's tweak our banking example. Instead of just saying "Not enough money," let's tell the caller exactly how much is missing:
public class InsufficientFundsException : Exception
{
public decimal CurrentBalance { get; }
public decimal AttemptedWithdrawal { get; }
public InsufficientFundsException(decimal balance, decimal attempted)
: base($"Attempted to withdraw {attempted:C}, but balance is only {balance:C}.")
{
CurrentBalance = balance;
AttemptedWithdrawal = attempted;
}
}
Now, in your UI layer, you can catch this exception and use ex.AttemptedWithdrawal - ex.CurrentBalance to tell the user exactly how much more they need to deposit. It turns a vague error into a useful piece of data.
📋 Practical Task
Implement a Credit Limit Guard for a Virtual Wallet
You are working on a virtual wallet system. You need to ensure that users cannot spend more than their assigned credit limit.
- Create a custom exception class named
CreditLimitExceededException. - Add a read-only property
CreditLimit(decimal) to the exception. - Create a
Walletclass with adecimal Balanceand adecimal CreditLimit. - Implement a
Spend(decimal amount)method. If theamountexceeds theBalance + CreditLimit, throw your custom exception, passing the current limit into the constructor. - In your
Mainmethod, wrap theSpendcall in atry-catchblock that specifically catchesCreditLimitExceededExceptionand prints a message: "Transaction declined. Your credit limit is [Limit]."
There are no comments for now.