It seems that the OP's intention was to find a good pattern for solving his issue and resolving the current problem that he was struggling with at that moment.
OP: "I could wrap each calculation in a helper method which returns null on failure,
and then just use the ??
operator, but is there a way of doing this more generally
(i.e. without having to write a helper method for each method I want to use)?
I've thought about writing a static method using generics which wraps any given
method in a try/catch and returns null on failure,
but I'm not sure how I would go about this. Any ideas?"
I saw a lot of good patterns that avoid nested try catch blocks, posted in this feed, but didn't find a solution to the problem that is cited above.
So, here is the solution:
As OP mentioned above, he wanted to make a wrapper object which returns null
on failure.
I would call it a pod (Exception-safe pod).
public static void Run()
{
// The general case
// var safePod1 = SafePod.CreateForValueTypeResult(() => CalcX(5, "abc", obj));
// var safePod2 = SafePod.CreateForValueTypeResult(() => CalcY("abc", obj));
// var safePod3 = SafePod.CreateForValueTypeResult(() => CalcZ());
// If you have parameterless functions/methods, you could simplify it to:
var safePod1 = SafePod.CreateForValueTypeResult(Calc1);
var safePod2 = SafePod.CreateForValueTypeResult(Calc2);
var safePod3 = SafePod.CreateForValueTypeResult(Calc3);
var w = safePod1() ??
safePod2() ??
safePod3() ??
throw new NoCalcsWorkedException(); // I've tested it on C# 7.2
Console.Out.WriteLine($"result = {w}"); // w = 2.000001
}
private static double Calc1() => throw new Exception("Intentionally thrown exception");
private static double Calc2() => 2.000001;
private static double Calc3() => 3.000001;
But what if you'd like to create a safe pod for a Reference Type result returned by CalcN() functions/methods.
public static void Run()
{
var safePod1 = SafePod.CreateForReferenceTypeResult(Calc1);
var safePod2 = SafePod.CreateForReferenceTypeResult(Calc2);
var safePod3 = SafePod.CreateForReferenceTypeResult(Calc3);
User w = safePod1() ?? safePod2() ?? safePod3();
if (w == null) throw new NoCalcsWorkedException();
Console.Out.WriteLine($"The user object is {{{w}}}"); // The user object is {Name: Mike}
}
private static User Calc1() => throw new Exception("Intentionally thrown exception");
private static User Calc2() => new User { Name = "Mike" };
private static User Calc3() => new User { Name = "Alex" };
class User
{
public string Name { get; set; }
public override string ToString() => $"{nameof(Name)}: {Name}";
}
So, you might notice that there is no need "to write a helper method for each method you want to use".
The two types of pods (for ValueTypeResult
s and ReferenceTypeResult
s) are enough.
Here is the code of SafePod
. It isn't a container though. Instead, it creates an exception-safe delegate wrapper for both ValueTypeResult
s and ReferenceTypeResult
s.
public static class SafePod
{
public static Func<TResult?> CreateForValueTypeResult<TResult>(Func<TResult> jobUnit) where TResult : struct
{
Func<TResult?> wrapperFunc = () =>
{
try { return jobUnit.Invoke(); } catch { return null; }
};
return wrapperFunc;
}
public static Func<TResult> CreateForReferenceTypeResult<TResult>(Func<TResult> jobUnit) where TResult : class
{
Func<TResult> wrapperFunc = () =>
{
try { return jobUnit.Invoke(); } catch { return null; }
};
return wrapperFunc;
}
}
That's how you can leverage the null-coalescing operator ??
combined with the power of first-class citizen entities (delegate
s).