Errors in C#

In C#, error handling is typically done using exceptions, which is different from the approach used in some other languages. However, we can implement a similar pattern using custom types and methods. Here’s how we might structure error handling in C#:

using System;

public class CustomError : Exception
{
    public CustomError(string message) : base(message) { }
}

public static class Errors
{
    public static readonly CustomError OutOfTea = new CustomError("no more tea available");
    public static readonly CustomError Power = new CustomError("can't boil water");
}

class Program
{
    static (int, Exception) F(int arg)
    {
        if (arg == 42)
        {
            return (-1, new CustomError("can't work with 42"));
        }
        return (arg + 3, null);
    }

    static Exception MakeTea(int arg)
    {
        if (arg == 2)
        {
            return Errors.OutOfTea;
        }
        else if (arg == 4)
        {
            return new CustomError($"making tea: {Errors.Power.Message}");
        }
        return null;
    }

    static void Main()
    {
        foreach (int i in new int[] { 7, 42 })
        {
            var (result, error) = F(i);
            if (error != null)
            {
                Console.WriteLine($"f failed: {error.Message}");
            }
            else
            {
                Console.WriteLine($"f worked: {result}");
            }
        }

        for (int i = 0; i < 5; i++)
        {
            var err = MakeTea(i);
            if (err != null)
            {
                if (err == Errors.OutOfTea)
                {
                    Console.WriteLine("We should buy new tea!");
                }
                else if (err.Message.Contains(Errors.Power.Message))
                {
                    Console.WriteLine("Now it is dark.");
                }
                else
                {
                    Console.WriteLine($"unknown error: {err.Message}");
                }
                continue;
            }
            Console.WriteLine("Tea is ready!");
        }
    }
}

In this C# version:

  1. We define a CustomError class that inherits from Exception to represent our custom errors.

  2. We create static CustomError instances in the Errors class to represent sentinel errors.

  3. The F method returns a tuple of (int, Exception) to mimic Go’s multiple return values.

  4. We use null to indicate no error, similar to Go’s nil.

  5. The MakeTea method returns an Exception or null.

  6. In the Main method, we use pattern matching to check for errors and handle them accordingly.

  7. We use exception.Message.Contains() to check for wrapped errors, as C# doesn’t have a direct equivalent to Go’s errors.Is.

This approach provides a similar error-handling pattern to the Go example, allowing for explicit error checking and handling. However, it’s worth noting that this is not idiomatic C# code. In real C# applications, it’s more common to use try-catch blocks and throw exceptions for error handling.

When you run this program, you should see output similar to:

f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

This example demonstrates how to implement a similar error-handling pattern in C#, but keep in mind that C#’s standard approach to error handling is using exceptions.