Insight into Programming
Typescript-vs-CSharp
Error Handling

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 an Error object, which is caught by the catch block. The error variable is implicitly typed as any, as TypeScript does not enforce specific exception types.

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 potential Error, and TypeScript’s type system does not indicate that the function may throw.

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 (with strict mode) requires a type guard (e.g., instanceof Error) to safely access properties like message.

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 that validateUser throws ValidationError.

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 the try or catch, useful for cleanup operations.

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 specifies DivideByZeroException, ensuring only that exception type is caught. The ex variable is strongly typed.

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 an InvalidOperationException, but C# does not require callers to handle it. However, .NET APIs often document expected exceptions (e.g., in XML comments).

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 generic Exception as a fallback. The compiler ensures the caught type is a valid Exception subclass.

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 the catch block explicitly handles it, leveraging C#’s type system.

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 the try block completes normally.

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 like Message.

Key Differences Summarized with Examples

  1. Exception Handling Mechanism:

    • TypeScript: Uses JavaScript’s try/catch, catching any thrown value (not just Error objects).
      try {
          throw "Something went wrong"; // Throws a string
      } catch (error) {
          console.log(error); // Output: Something went wrong
      }
    • C#: Uses try/catch/finally, catching only Exception-derived types.
      try {
          throw new Exception("Something went wrong");
      } catch (Exception ex) {
          Console.WriteLine(ex.Message); // Output: Something went wrong
      }
  2. 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
  3. Type System Integration:

    • TypeScript: Exceptions are not part of the type system; catch variables are any or unknown.
      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
      }
  4. 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
      }
  5. 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
      }
  6. 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
      }

Difference Table

FeatureTypeScriptC#
MechanismUses JavaScript’s try/catch, catching any value.Uses try/catch/finally, catching only Exception-derived types.
Exampletry { throw "Error"; } catch (e) { console.log(e); }try { throw new Exception("Error"); } catch (Exception ex) { Console.WriteLine(ex.Message); }
Checked ExceptionsNo checked exceptions; no declaration or handling required.No checked exceptions, but APIs document exceptions.
Examplefunction risky() { throw new Error("Fail"); } risky();public static void Risky() { throw new InvalidOperationException(); } Risky();
Type SystemExceptions not part of type system; catch uses any/unknown.Strongly-typed exceptions, part of System.Exception hierarchy.
Examplecatch (error: unknown) { if (error instanceof Error) { ... } }catch (ArgumentException ex) { Console.WriteLine(ex.Message); }
Custom ExceptionsExtend Error, but not enforced by type system.Inherit from Exception, commonly used with type-safe handling.
Exampleclass CustomError extends Error {} throw new CustomError("Fail");public class CustomException : Exception { ... } throw new CustomException("Fail");
Finally BlockSupported (ES2015+), less common in JavaScript code.Standard feature, widely used for cleanup.
Exampletry { throw new Error(); } catch {} finally { console.log("Done"); }try { throw new Exception(); } catch {} finally { Console.WriteLine("Done"); }
Exception FiltersNo filters; uses type guards for conditional handling.Supports when clause for filtering exceptions.
Exampleif (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. The catch block uses any or unknown, 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 like finally 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.