The Exception That Wasn't Really There
TL;DR: A custom exception wrapper that throws exceptions while trying to safely handle exceptions
The Code
1namespace Func;
2
3public record Ex<T>
4{
5 private Ex() { }
6 public Exception? Error { get; }
7 public T? Value { get; }
8 public bool IsSuccess { get; }
9 public bool IsException => !IsSuccess;
10
11 public Ex(Exception ex)
12 {
13 IsSuccess = false;
14 Error = ex ?? throw new ArgumentNullException(nameof(ex)); // Throws exception!
15 Value = default;
16 }
17
18 public Ex(T value)
19 {
20 if (value != null)
21 {
22 IsSuccess = true;
23 Value = value;
24 }
25 else
26 {
27 IsSuccess = false;
28 Error = new Exception(string.Format(Lang.Resources.ArgumentNullOrEmpty, value)); // Creates exception for null!
29 }
30 }
31
32 // ... (additional code omitted)
33}
34
35public static class ExE
36{
37 public static Ex<R> Map<A, R>(this Ex<A> @this, Func<A, R> f)
38 {
39 try
40 {
41 if (@this.IsSuccess)
42 return f(@this.Value!);
43 else
44 return @this.Error!;
45 }
46 catch (Exception e)
47 {
48 return e; // More exception handling in "safe" wrapper
49 }
50 }
51
52 // ... (additional methods with similar patterns omitted)
53}
54
55*(Excerpt from larger file - showing relevant section only)*
56
57The Prayer 🙏
🙏 Dear Code Gods, please bless this exception wrapper that throws exceptions in its exception handling logic. May the try-catch blocks within try-catch blocks protect us from the exceptions thrown by our exception protection system. Amen.
The Reality Check
This code attempts to create a railway-oriented programming pattern but spectacularly misses the point by throwing exceptions everywhere. The constructor throws an ArgumentNullException when trying to wrap null exceptions, defeating the entire purpose of safe error handling. Multiple methods have try-catch blocks that catch exceptions just to... throw new exceptions.
In production, this would create a debugging nightmare. When your "safe" error handling throws exceptions, you've essentially created exception inception - exceptions all the way down. The LINQ extensions and collection methods would fail unpredictably, making it impossible to trust that any operation is actually safe.
The irony is palpable: developers would use this thinking they're writing robust, functional-style error handling, only to discover their "safe" operations are crashing with NullReferenceExceptions and ArgumentNullExceptions. The whole system becomes less reliable than just using regular try-catch blocks.
The Fix
The core issue is trying to have it both ways - you can't create an exception-free wrapper that throws exceptions. Here's how to fix the constructor:
1public Ex(T value)
2{
3 if (value != null)
4 {
5 IsSuccess = true;
6 Value = value;
7 Error = null;
8 }
9 else
10 {
11 IsSuccess = false;
12 Error = new ArgumentNullException(nameof(value));
13 Value = default;
14 }
15}
16Better yet, embrace the functional approach completely with static factory methods:
1public static Ex<T> Success(T value) => new() { IsSuccess = true, Value = value, Error = null };
2public static Ex<T> Failure(Exception error) => new() { IsSuccess = false, Value = default, Error = error };
3public static Ex<T> Try(Func<T> operation)
4{
5 try { return Success(operation()); }
6 catch (Exception ex) { return Failure(ex); }
7}
8The key insight is that once you're in the "safe" wrapper world, you should never throw exceptions again. Every operation should return another wrapped result, creating a true railway where errors flow safely through your pipeline without derailing the train.
Lesson Learned
Exception-safe wrappers that throw exceptions are like safety nets with holes in them.