Let’s dive into a detailed comparison of error handling in TypeScript and C#, focusing on their mechanisms for handling exceptions, the role of the type system, and their runtime behavior. I’ll explain how TypeScript relies on JavaScript’s try
/catch
mechanism without checked exceptions or type enforcement for exceptions, and how C# uses try
/catch
/finally
with strongly-typed exceptions integrated into the .NET type system. Each point will be illustrated with clear, concise examples in a pointwise manner, followed by a difference table summarizing the key distinctions.
TypeScript: Error Handling
TypeScript, as a superset of JavaScript, inherits JavaScript’s error handling model, using try
/catch
to handle exceptions. There are no checked exceptions (exceptions that must be declared or caught), and the type system does not enforce or track exception types, reflecting JavaScript’s dynamic nature. Errors are typically instances of the Error
class or its subclasses, but TypeScript does not require specifying exception types in function signatures.
1. Basic try/catch
TypeScript uses try
/catch
to handle runtime errors, catching any value thrown (not limited to Error
objects).
- Example:
function divide(a: number, b: number): number { if (b === 0) { throw new Error("Division by zero"); } return a / b; } try { const result = divide(10, 0); console.log(result); } catch (error) { console.log(error.message); // Output: Division by zero }
- How it Works: The
throw
statement raises anError
object, which is caught by thecatch
block. Theerror
variable is implicitly typed asany
, as TypeScript does not enforce specific exception types.
- How it Works: The
2. No Checked Exceptions
TypeScript does not have checked exceptions, meaning functions are not required to declare what exceptions they might throw, and callers are not forced to handle them.
- Example:
function riskyOperation(): string { if (Math.random() > 0.5) { throw new Error("Random failure"); } return "Success"; } // No need to wrap in try/catch const result = riskyOperation(); console.log(result); // Output: Success (or throws uncaught error)
- How it Works: Callers of
riskyOperation
are not obligated to handle the potentialError
, and TypeScript’s type system does not indicate that the function may throw.
- How it Works: Callers of
3. Type System and Exceptions
TypeScript’s type system does not track or enforce exception types, so the catch
block’s error parameter is typed as any
or unknown
(with strict
mode). Developers can use type guards to handle specific error types.
- Example:
function processData(data: string | null): string { if (!data) { throw new Error("Invalid data"); } return data.toUpperCase(); } try { const result = processData(null); console.log(result); } catch (error: unknown) { if (error instanceof Error) { console.log(error.message); // Output: Invalid data } else { console.log("Unknown error"); } }
- How it Works: The
error: unknown
type (withstrict
mode) requires a type guard (e.g.,instanceof Error
) to safely access properties likemessage
.
- How it Works: The
4. Custom Error Types
Developers can create custom error classes by extending Error
, but TypeScript does not enforce their use in function signatures.
- Example:
class ValidationError extends Error { constructor(message: string) { super(message); this.name = "ValidationError"; } } function validateUser(user: { name: string } | null): void { if (!user) { throw new ValidationError("User cannot be null"); } } try { validateUser(null); } catch (error) { if (error instanceof ValidationError) { console.log(error.message); // Output: User cannot be null } else { console.log("Other error"); } }
- How it Works:
ValidationError
is a custom error type, but TypeScript does not require specifying thatvalidateUser
throwsValidationError
.
- How it Works:
5. No finally
Block
TypeScript’s try
/catch
(inherited from JavaScript) does not include a finally
block by default in older environments, though modern JavaScript (ES2015+) supports it. The finally
block runs regardless of whether an exception is thrown.
- Example:
function tryResource() { try { console.log("Trying resource"); throw new Error("Resource error"); } catch (error) { console.log(error.message); // Output: Resource error } finally { console.log("Cleaning up"); // Output: Cleaning up } } tryResource();
- How it Works: The
finally
block executes after thetry
orcatch
, useful for cleanup operations.
- How it Works: The
C#: Error Handling
C# uses a structured try/catch/finally mechanism for exception handling, with strongly-typed exceptions derived from the System.Exception
class. C# does not have checked exceptions, but exceptions are part of the .NET type system, and APIs often document expected exception types. The finally
block is standard for cleanup, and exceptions are enforced at both compile-time (via type checking) and runtime.
1. Basic try/catch
C# uses try
/catch
to handle exceptions, with the catch
block specifying the type of exception to catch, leveraging the .NET type system.
- Example:
public class Program { public static int Divide(int a, int b) { if (b == 0) { throw new DivideByZeroException("Division by zero"); } return a / b; } static void Main() { try { int result = Divide(10, 0); Console.WriteLine(result); } catch (DivideByZeroException ex) { Console.WriteLine(ex.Message); // Output: Division by zero } } }
- How it Works: The
catch
block specifiesDivideByZeroException
, ensuring only that exception type is caught. Theex
variable is strongly typed.
- How it Works: The
2. No Checked Exceptions
Like TypeScript, C# does not have checked exceptions, so methods are not required to declare exceptions they might throw, and callers are not forced to handle them.
- Example:
public class Program { public static string RiskyOperation() { if (DateTime.Now.Second % 2 == 0) { throw new InvalidOperationException("Operation failed"); } return "Success"; } static void Main() { // No need to wrap in try/catch string result = RiskyOperation(); Console.WriteLine(result); // Output: Success (or throws uncaught exception) } }
- How it Works:
RiskyOperation
may throw anInvalidOperationException
, but C# does not require callers to handle it. However, .NET APIs often document expected exceptions (e.g., in XML comments).
- How it Works:
3. Strongly-Typed Exceptions
C# exceptions are part of the type system, inheriting from System.Exception
. Catch blocks can specify exact exception types, and multiple catch
blocks can handle different exceptions.
- Example:
public class Program { public static void ProcessData(string data) { if (data == null) { throw new ArgumentNullException(nameof(data)); } if (data == "") { throw new ArgumentException("Data cannot be empty", nameof(data)); } } static void Main() { try { ProcessData(null); } catch (ArgumentNullException ex) { Console.WriteLine(ex.Message); // Output: Value cannot be null. (Parameter 'data') } catch (ArgumentException ex) { Console.WriteLine(ex.Message); // Not reached in this case } catch (Exception ex) { Console.WriteLine("Unexpected error: " + ex.Message); } } }
- How it Works: Multiple
catch
blocks allow handling specific exception types, with the genericException
as a fallback. The compiler ensures the caught type is a validException
subclass.
- How it Works: Multiple
4. Custom Exception Types
C# allows creating custom exception classes by inheriting from Exception
, commonly used in .NET APIs for specific error scenarios.
- Example:
public class ValidationException : Exception { public ValidationException(string message) : base(message) {} } public class Program { public static void ValidateUser(string user) { if (string.IsNullOrEmpty(user)) { throw new ValidationException("User cannot be null or empty"); } } static void Main() { try { ValidateUser(""); } catch (ValidationException ex) { Console.WriteLine(ex.Message); // Output: User cannot be null or empty } } }
- How it Works:
ValidationException
is a custom type, and thecatch
block explicitly handles it, leveraging C#’s type system.
- How it Works:
5. finally Block
C#’s try
/catch
/finally
includes a finally
block that runs regardless of whether an exception is thrown, ideal for cleanup operations.
- Example:
public class Program { static void Main() { try { Console.WriteLine("Trying resource"); throw new Exception("Resource error"); } catch (Exception ex) { Console.WriteLine(ex.Message); // Output: Resource error } finally { Console.WriteLine("Cleaning up"); // Output: Cleaning up } } }
- How it Works: The
finally
block ensures cleanup code runs, even if an exception occurs or thetry
block completes normally.
- How it Works: The
6. Exception Filters
C# supports exception filters (using when
) to catch exceptions based on conditions, a feature not available in TypeScript.
- Example:
public class Program { static void Main() { try { throw new Exception("Error with code 42"); } catch (Exception ex) when (ex.Message.Contains("42")) { Console.WriteLine("Caught error with code 42"); // Output: Caught error with code 42 } catch (Exception ex) { Console.WriteLine("Other error"); } } }
- How it Works: The
when
clause filters exceptions, allowing precise handling based on properties likeMessage
.
- How it Works: The
Key Differences Summarized with Examples
-
Exception Handling Mechanism:
- TypeScript: Uses JavaScript’s
try
/catch
, catching any thrown value (not justError
objects).try { throw "Something went wrong"; // Throws a string } catch (error) { console.log(error); // Output: Something went wrong }
- C#: Uses
try
/catch
/finally
, catching onlyException
-derived types.try { throw new Exception("Something went wrong"); } catch (Exception ex) { Console.WriteLine(ex.Message); // Output: Something went wrong }
- TypeScript: Uses JavaScript’s
-
Checked Exceptions:
- TypeScript: No checked exceptions; functions do not declare thrown exceptions.
function risky(): void { throw new Error("Failure"); } risky(); // No requirement to handle
- C#: No checked exceptions, but APIs often document exceptions in comments.
/// <exception cref="InvalidOperationException">Thrown if operation fails.</exception> public static void Risky() { throw new InvalidOperationException("Failure"); } Risky(); // No requirement to handle
- TypeScript: No checked exceptions; functions do not declare thrown exceptions.
-
Type System Integration:
- TypeScript: Exceptions are not part of the type system;
catch
variables areany
orunknown
.try { throw new Error("Test"); } catch (error: unknown) { if (error instanceof Error) { console.log(error.message); // Output: Test } }
- C#: Exceptions are strongly-typed, part of the
System.Exception
hierarchy.try { throw new ArgumentException("Test"); } catch (ArgumentException ex) { Console.WriteLine(ex.Message); // Output: Test }
- TypeScript: Exceptions are not part of the type system;
-
Custom Exceptions:
- TypeScript: Custom errors extend
Error
, but their use is not enforced by the type system.class CustomError extends Error {} try { throw new CustomError("Custom"); } catch (error) { console.log(error instanceof CustomError ? "Custom error" : "Other"); // Output: Custom error }
- C#: Custom exceptions inherit from
Exception
, commonly used in APIs with type-safe handling.public class CustomException : Exception { public CustomException(string msg) : base(msg) {} } try { throw new CustomException("Custom"); } catch (CustomException ex) { Console.WriteLine(ex.Message); // Output: Custom }
- TypeScript: Custom errors extend
-
Finally Block:
- TypeScript: Supports
finally
(ES2015+), but it’s less common in JavaScript-heavy code.try { throw new Error("Error"); } catch { console.log("Caught"); } finally { console.log("Finally"); // Output: Finally }
- C#:
finally
is a standard feature, widely used for cleanup.try { throw new Exception("Error"); } catch { Console.WriteLine("Caught"); } finally { Console.WriteLine("Finally"); // Output: Finally }
- TypeScript: Supports
-
Exception Filters:
- TypeScript: No exception filters; conditional handling requires type guards.
try { throw new Error("Code: 42"); } catch (error: unknown) { if (error instanceof Error && error.message.includes("42")) { console.log("Error 42"); // Output: Error 42 } }
- C#: Supports exception filters with
when
for precise handling.try { throw new Exception("Code: 42"); } catch (Exception ex) when (ex.Message.Contains("42")) { Console.WriteLine("Error 42"); // Output: Error 42 }
- TypeScript: No exception filters; conditional handling requires type guards.
Difference Table
Feature | TypeScript | C# |
---|---|---|
Mechanism | Uses JavaScript’s try /catch , catching any value. | Uses try /catch /finally , catching only Exception -derived types. |
Example | try { throw "Error"; } catch (e) { console.log(e); } | try { throw new Exception("Error"); } catch (Exception ex) { Console.WriteLine(ex.Message); } |
Checked Exceptions | No checked exceptions; no declaration or handling required. | No checked exceptions, but APIs document exceptions. |
Example | function risky() { throw new Error("Fail"); } risky(); | public static void Risky() { throw new InvalidOperationException(); } Risky(); |
Type System | Exceptions not part of type system; catch uses any /unknown . | Strongly-typed exceptions, part of System.Exception hierarchy. |
Example | catch (error: unknown) { if (error instanceof Error) { ... } } | catch (ArgumentException ex) { Console.WriteLine(ex.Message); } |
Custom Exceptions | Extend Error , but not enforced by type system. | Inherit from Exception , commonly used with type-safe handling. |
Example | class CustomError extends Error {} throw new CustomError("Fail"); | public class CustomException : Exception { ... } throw new CustomException("Fail"); |
Finally Block | Supported (ES2015+), less common in JavaScript code. | Standard feature, widely used for cleanup. |
Example | try { throw new Error(); } catch {} finally { console.log("Done"); } | try { throw new Exception(); } catch {} finally { Console.WriteLine("Done"); } |
Exception Filters | No filters; uses type guards for conditional handling. | Supports when clause for filtering exceptions. |
Example | if (error instanceof Error && error.message.includes("42")) { ... } | catch (Exception ex) when (ex.Message.Contains("42")) { ... } |
Summary
- TypeScript Error Handling: Relies on JavaScript’s
try
/catch
, with no checked exceptions or type system enforcement for exceptions. Thecatch
block usesany
orunknown
, requiring type guards for safety. This aligns with JavaScript’s dynamic nature, making TypeScript suitable for web applications where flexibility is key. - C# Error Handling: Uses
try
/catch
/finally
with strongly-typed exceptions, integrated into the .NET type system. While not checked, exceptions are documented in APIs, and features likefinally
and exception filters provide robust control. This makes C# ideal for enterprise applications requiring precise error handling.
These differences reflect TypeScript’s focus on enhancing JavaScript with type safety for dynamic environments, versus C#’s emphasis on structured, type-safe error handling in the .NET ecosystem.