CustomizableTryParadigm.Release 0.0.10

dotnet add package CustomizableTryParadigm.Release --version 0.0.10                
NuGet\Install-Package CustomizableTryParadigm.Release -Version 0.0.10                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CustomizableTryParadigm.Release" Version="0.0.10" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add CustomizableTryParadigm.Release --version 0.0.10                
#r "nuget: CustomizableTryParadigm.Release, 0.0.10"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install CustomizableTryParadigm.Release as a Cake Addin
#addin nuget:?package=CustomizableTryParadigm.Release&version=0.0.10

// Install CustomizableTryParadigm.Release as a Cake Tool
#tool nuget:?package=CustomizableTryParadigm.Release&version=0.0.10                

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 Exception to be thrown in case of "Try" method failure into a Tuple(T_Code?, T_Out?, Exception?)

  • Handling such tuple to either simply return the T_Out value in case of success (dertermined by T_Code value) 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.

Customize exceptions handling

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.

  • CustomBeforeExceptionHandler that will actually be called inside the previously described ExceptionToTuple() method on the "Try" method returning exception in case of failure of this method.

  • CustomExceptionHandler that will be called on the so called "root" exception (with the "Try" method exception as its InnerException). This is where the actual custom exception handling will take place.

  • CustomAfterExceptionHandler if you ever need a post exception handling process to happen on the "root" exception.

  • CustomNotHandledExceptionHandler define how exceptions that are not handled by this library (those whose types aren't in the HandledExceptions collection) should be ... handled (kinda ironic).


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(...).


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!")
  );

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 Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.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.10 82 10/25/2024
0.0.9 78 10/25/2024
0.0.8 83 10/23/2024
0.0.7 88 10/22/2024
0.0.6 96 10/22/2024

Further customize our handlers with an enum flags that can be easily set to modify on the fly the behaviour of the handlers.