CustomizableTryParadigm.Debug
0.0.15
dotnet add package CustomizableTryParadigm.Debug --version 0.0.15
NuGet\Install-Package CustomizableTryParadigm.Debug -Version 0.0.15
<PackageReference Include="CustomizableTryParadigm.Debug" Version="0.0.15" />
<PackageVersion Include="CustomizableTryParadigm.Debug" Version="0.0.15" />
<PackageReference Include="CustomizableTryParadigm.Debug" />
paket add CustomizableTryParadigm.Debug --version 0.0.15
#r "nuget: CustomizableTryParadigm.Debug, 0.0.15"
#:package CustomizableTryParadigm.Debug@0.0.15
#addin nuget:?package=CustomizableTryParadigm.Debug&version=0.0.15
#tool nuget:?package=CustomizableTryParadigm.Debug&version=0.0.15
Customizable Try-Paradigm implementation
Motivation
When developping my DTO Infrastructure framework, I ended up adopting the, as I call it, Try-Paradigm for my methods. This "paradigm" purpose is to transform any methom into its "Try" version, where, instead of throwing an exception, return it with a corresponding boolean indication the failure or success of this "Try" method, and also with its returned value if applicable.
I used a Tuple(bool, T?, Exception?) in order to represent such data to be returned by those "Try" method. This Tuple could then be processed to check upton the boolean value if we get the returning value or if we need to handle the exception.
Eventually I ended up refactoring my code to add delegates to allow customization of how to handle this exception, then I realize I should make it as a stand alone library instead, so here is the result of it.
Description
This library revolve around 3 main parts :
Formating any returning value or
Exceptionto be thrown in case of "Try" method failure into aTuple(T_Code?, T_Out?, Exception?)Handling such tuple to either simply return the
T_Outvalue in case of success (dertermined byT_Codevalue) or perform a custom exception handling.Exposing 4
Action<Exception?>delegates to allow customization of such handling mechanism.
Returning tuples for "Try" method
This library offer user to call the CustomCatchUtility.ReturningTryTuple(this Exception?, ...) extension method and their overloaded variant to transmute any Exception along with the potential T_Out returning value of the "Try" method into a corresponding Tuple(bool, T_Out?, Exception?).
In this implementation though, I ended up being a little more general purpose and abstracted away the returning boolean as a generic T_Code Type is ever you want to check such "Try" method success or failure along another Type than a boolean (I had in mind ternary value Type). The returned tuple is then a Tuple(T_Code?, T_Out?, Exception?).
The overloaded variants are methods that either use bool as T_Code and/or no T_Out value are returned in a first place, so returning a Tuple(T_Code?, Exception?) instead.
The generic T_Code variant require that you provide a Func<Exception?, T_Out?, bool, T_Code?> delegate that will compute the correct T_Code code status to return given the Exception and T_Out arguments of ExceptionToTuple() and boolean telling if a NULL T_Out value is a correct returning value for the "Try" method or not.
This method and their overloaded variant are
(bool, Exception?) ReturningTryTuple(this Exception? exception)(T_Code?, Exception?) ReturningTryTuple<T_Code>(this Exception? exception, Func<Exception?, NoOutValue?, bool, T_Code?> returnCodeDelegate)(bool, T_Out?, Exception?) ReturningTryTuple<T_Out>(this Exception? exception, T_Out? outValue = default, bool outCanBeNull = false)(T_Code?, T_Out?, Exception?) ReturningTryTuple<T_Code, T_Out>(this Exception? exception, Func<Exception?, T_Out?, bool, T_Code?> returnCodeDelegate, T_Out? outValue = default, bool outCanBeNull = false)
where NoOutValue is just an empty struct to mimic a non returning "Try" method (void or Task method).
In order to offer a filter on exceptions that can be handled or not by this library, it expose a collection of Exception Type, wich are all the types, and automaticcaly all of their corresponding sub-types, that are handled by this library.
This collection is the exposed HandledExceptions property, wich is an CustomAddHashSet<Type> that allow adding only Type that are sub-types of the top-most Exception type, that contains by default the defined abstract CustomCatchableException class. So if you want your custom exceptions to be handled by this library by default, you'll have to extend this CustomCatchableException class instead of directly extend Exception.
If this behavior doen't match your requirement, and/or you want to handle other exceptions you don't own, you can add them to HandledExceptions, or more concisely their common exception type ancestor. That is to say to adding Exception directly will encapsulate all exception then. You can call the HandleAllExceptions() method to do so.
Handling "Try" method and their returned tuples
This library offer T_Out? HandleTryMethod<T_Code, T_Out>(this Func<(T_Code?, T_Out?, Exception?)> tryMethod, Func<T_Code?, bool> checkCodeDelegate, Exception? exception = default) as a method to transfom any "Try" methods to their standard versions that retun direcly only its return value (or void if no value where returned in a first place).
You will need to provide the so said "Try" method along with a Func<T_Code?, bool> delegate that will interpret the returning T_Code code status into a boolean indication success or failure of the "Try" method. Not necessary, but you can provide a "root" exception that will be customly handled here. If none are provided, a standard Exception will be created then. Either way, this resulting exception will encapsulate the "Try" method returning exception as its InnerException before being handled.
Exceptions handlers
In case of failure of the corresponding "Try" method, the returning Tuple(T_Code?, T_Out?, Exception?) will be handled along 4 customizable exposed Action<Exception?> delegates.
CustomBeforeExceptionHandlerthat will actually be called inside the previously describedExceptionToTuple()method on the "Try" method returning exception in case of failure of this method.CustomExceptionHandlerthat will be called on the so called "root" exception (with the "Try" method exception as itsInnerException). This is where the actual custom exception handling will take place.CustomAfterExceptionHandlerif you ever need a post exception handling process to happen on the "root" exception.CustomNotHandledExceptionHandlerdefine how exceptions that are not handled by this library (those whose types aren't in theHandledExceptionscollection) should be ... handled (kinda ironic).
Customize exceptions handling
You can either customize those handlers delegates by setting them with your own logic (code), or use the default implementations provided by this library.
The defaults implementations are parametrized by some flags to change their behaviours at runtime. Here are the 4 differents defined flags :
ThrowHandledExceptionif set, this flag will allow defaultCustomExceptionHandlerimplementation to throw caught exceptions handled by this library. If not, no exception throwing will happen.ThrowUnhandledExceptionif set, this flag will allow defaultCustomNotHandledExceptionHandlerimplementation to throw caught exceptions not handled by this library. If not, no exception throwing will happen.DIAGNOSTICif set, this flag will perform aDiagnostic(wrappedTraceactually) logging of each custom delegates calls. It also enable/disable calling correspondingDiagnosticsUtility.WriteLine|WithStackTrace()methods.
CustomizableTryParadigm.Debug define such DIAGNOSTIC flag constant, whereas CustomizableTryParadigm.Release doesn't.
This mean using Debug version of the package will trace all returning try tuple automatically.
This is particulary usefull when you need those custom delegates to finally throw caught exceptions on general purpose, but still want to prevent any trhowing on some specific cases. Do ease this flags management, the library expose 2 method :
ToggleThrowingBehaviour(allowThrowing)ToggleLogBehaviour(allowTrace, allowDebug)
wich will toogle on and off their corresponding flags.
Usage
For any "Try" method you are writing, when you decide that the method fail, instead of throwing an Exception, call one of the overload of exception.ReturningTryTuple(...) on it , or in case of success directly CustomCatchUtility.ReturningTryTuple(default, yourReturningingvalue, canBeNull), depending on what tuple you want to return (see corresponding previous section). This way all your "Try" method will return a tuple ready to be handled by the next main method of this library. If you choose to use another code status than a boolean, then you'll need to provide a delegate to compute such custom code status from the same parameter than those of ReturningTryTuple(...).
To handle your "Try" methods, you simple have to create a new methods for each of them that will call CustomCatchUtility.HandleTryMethod(...) on them along with the forwarded "Try" method arguments and an optional exception to be customly handled in case of failure of the "Try" method. If you choose to use another code status than a boolean, you'll need to provide a delegate that will interprete such custom code status into a boolean value.
To customize how the library will perform its handling, you simply set with your own exception handling logic any or all of the 4 exception delegates decribed in the previous section.
You can call CustomCatchUtility.HandleAllExceptions() before to handle all kind of exceptions. If not, only those derivating from CustomCatchableException class will be handled this way.
Finally there are asynchronous variants for each of the defined HandleTryMethod(...) called HandleTryMethodAsync(...).
Then either you want to stick with provided default handlers implementation, and then you can still alter their behaviours via setting the HandlersBehaviour property's flads values, or write your own logic code and set it to the corresponding handlers delegates.
Exemple
Let say you want a method, int GetCapacity<T>(IEnumerable<T>) that will return the capacity (Count) of the IEnumerable<T> argument.
Instead of coding it directly, you can define the logic in another method, preferably called TryGetCapacity<T>(IEnumerable<T>) that will not return an int but a (bool, int, Exception?) tuple instead. To do so, whenever an Exception occur, lets name it ex, then return ex.ReturningTryTuple<int>();; or when you return a computed int capacity, instead return CustomCatchUtility.ReturningTryTuple(default, capacity).
public (bool, int, Exception?) TryGetCapacity<T>(IEnumerable<T> collection)
{
if (collection == null)
return new ArgumentNullException(nameof(collection))
.ReturningTryTuple<int>();
return CustomCatchUtility.ReturningTryTuple(default, collection.Count());
}
Then you can now define you original int GetCapacity<T>(IEnumerable<T>) method wich will simply call the HandleTryMethod(...) method on TryGetCapacity :
public int GetCapacity<T>(IEnumerable<T> collection)
=> CustomCatchUtility.HandleTryMethod
(
TryGetCapacity,
collection,
new CapacityException($"Call of {nameof(TryGetCapacity)}<{typeof(T).FullName}>({nameof(collection)}) failed!")
);
If you want to set your own logic for the handlers delegates, just set the corresponding delegate property :
CustomCatchUtility.CustomBeforeExceptionHandler =
(e) =>
{
string logMessage = e == default ?
$"""About to return from a "Try" method."""
: $"About to caught an {e} from {Assembly.GetExecutingAssembly().FullName?.Split(',')[0]}"
;
DiagnosticsUtility.WriteLine(logMessage);
};
If you want to stick with default handlers delegates implementation, you can alter their behaviours by setting CustomCatchUtility.HandlersBehaviour flag(s) :
CustomCatchUtility.HandlersBehaviour $= ~HandlersBehaviours.ThrowHandledException;
or use the toggle methods :
CustomCatchUtility.ToggleThrowingBehaviour(false);
You can further check the provided IntegrationTesting project along this library to see simple examples using the library.
Note
This library is available as a nuget package.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.0.15 | 139 | 7/8/2025 |
Updated readme to reflect switching to using DIAGNOSTIC constant flag from now on.