It's a fact of life that bad things sometimes happen. Tires get punctured, batteries run down, screwdrivers are never where you left them, and users of your applications behave in an unpredictable manner. Errors can occur at almost any stage when a program runs, so how do you detect them and attempt to recover? Over the years, a number of mechanisms have evolved. A typical approach adopted by older systems such as Unix involved arranging for the operating system to set a special global variable whenever a method failed. Then, after each call to a method, you checked the global variable to see whether the method failed. This solution has a number of shortcomings:
The main program logic becomes intertwined with alternating code to check and handle the errors. The program quickly becomes hard to understand.
The error checking and handling code is typically very repetitive and can easily double the size of the program. A large program is harder to understand than a small program simply because it's larger. Duplicate code is always a warning sign that an application can be structured in a better manner.
The error codes used by a global variable, such as –1 used by Unix systems, are not inherently meaningful. What does –1 mean? Integer error codes don't describe the errors they represent. They're very programmatic. Once again, the program becomes harder to understand and maintain.
It's just too easy to ignore the error condition and assume that a method being called works every time. Many programmers don't like to read documentation, and fewer like to write it, so the method might not be documented with information about the errors it can cause.
For these reasons, C# and most other modern object-oriented languages don't handle errors in this way. It's just too painful. They use exceptions instead. If you want to write robust C# programs, you need to know about exceptions.