Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Tip/Trick

An Exploration of Alternatives to if-then-else workflows

Rate me:
Please Sign up or sign in to vote.
4.91/5 (33 votes)
20 Jul 2020CPOL3 min read 60.1K   26   45
Alternatives to If-Then-Else with Extension Methods and Functional Programming Techniques
In this tip, you will see some alternative to if-then-else with extension methods

In this tip, we'll look at a couple of ways to work with if-then statements, using extension methods and FP techniques that improve code readability but require what might be considered obtuse or overly complex implementations. While I'm using C# as the language for the code examples, you can use these techniques in just about any language that supports passing functions.

Recently, I wrote a function to download a new version of the app. The flowchart looked like this:

Image 1

And the code basically looked like this (the use of static is simply because I'm illustrating this code with a simple console app):

C#
static void SinglePointOfReturn()
{
  bool ok = GetConfig();

  if (ok)
  {
    if (VersionChanged())
    {
      ok = DownloadVersion();

      if (ok)
      {
        ok = Unzip();

        if (ok)
        {
          ok = CopyFiles();

          if (ok)
          {
            RestartApp();
          }
          else
          {
            Error(ErrorType.Copy);
          }
        }
        else
        {
          Error(ErrorType.Unzip);
        }
      }
      else
      {
        Error(ErrorType.Download);
      }
    }
  }
  else
  {
    Error(ErrorType.Config);
  }
}

The above code has the advantage of a single point of return, but the nesting and if-then-else statements hide the overall flow. We can change the code, breaking the single point of return, to look something like this:

C#
static void MultipleReturnPoints()
{
  if (!GetConfig())
  {
    Error(ErrorType.Config);
    return;
  }

  if (!VersionChanged()) return;

  if (!DownloadVersion())
  {
    Error(ErrorType.Download);
    return;
  }

  if (!Unzip())
  {
    Error(ErrorType.Unzip);
    return;
  }

  if (!CopyFiles())
  {
    Error(ErrorType.Copy);
    return;
  }

  RestartApp();
}

This code is more linear and therefore a bit easier to read, but the multiple return points are not ideal in my opinion.

Using an extension method, we convert the if statement into a method:

C#
public static class Extensions
{
  public static bool If(this bool ok, Func<bool> fnc, Action onFail = null)
  {
    bool nextok = ok; // Preserve current state.

    if (ok) // if current state allows us to continue...
    {
      nextok = fnc();

      if (!nextok) // Continuation errored.
      {
        if (onFail != null) // If we have an error handler, call it.
        {
          onFail();
        }
      }
    }

    return nextok; // Return new state.
  }
}

Now we can write:

C#
static void IfAsAMethod()
{
  bool ok = true.If(GetConfig, () => Error(ErrorType.Config));
  ok = ok.If(VersionChanged);
  ok = ok.If(DownloadVersion, () => Error(ErrorType.Download));
  ok = ok.If(Unzip, () => Error(ErrorType.Unzip));
  ok = ok.If(CopyFiles, () => Error(ErrorType.Copy));
  ok.If(RestartApp);
}

As the above code illustrates, using an extension method to write the workflow in a more functional programming manner, we change the code that implements the workflow into something that is a clearer expression of the flowchart, although it looks strange to those uninitiated in FP techniques, and certainly converting a language feature, like an if statement, into a function, is not what you typically encounter either.

You might ask why we don't just pass in the error type.  The answer is that usually extension methods are put into libraries; passing the error type in to the extension method creates a dependency on the application's intention, both with the enumeration and having to be able to reference the Error handler function.  Using Func and Action decouples this dependency.

You might also ask, why each of the functions being called does not simply return the error type? This also creates a coupling of the extension method to the enumeration, which might be an acceptable compromise if the extension method is localized to just the namespace that handles the workflow. The enum could look like this:

C#
enum ErrorType
{
  None,
  Copy,
  Unzip,
  Download,
  Config,
}

Now note how the parameter signatures of the extension method are changed:

C#
public static ErrorType If(this ErrorType errorType, Func<ErrorType> fnc, 
                           Action<ErrorType> onFail = null)
{
  ErrorType nextError = errorType;   // Preserve current state.

  if (errorType == ErrorType.None)   // if current state allows us to continue...
  {
    nextError = fnc();

    if (nextError != ErrorType.None) // Continuation errored.
    {
      if (onFail != null)            // If we have an error handler, call it.
      {
        onFail(nextError);
      }
    }
  }

  return nextError;                  // Return new state.
}

The workflow is tightened up further:

C#
static void IfAsMethod2()
{
  ErrorType ok = ErrorType.None.If(GetConfig, Error);
  ok = ok.If(VersionChanged);
  ok = ok.If(DownloadVersion, Error);
  ok = ok.If(Unzip, Error);
  ok = ok.If(CopyFiles, Error);
  ok.If(RestartApp);
}

This is a good illustration of the tension between abstract implementation and implementations that are tailored to meet a specific need and are therefore less abstract, but less abstract means less re-usable.  So it's always a decision one should be aware of.

However, it still looks very atypical of procedural code, and the biggest problem with the code above is that, if any function fails (returns false), the If extension method is still evaluated for the remainder of the workflow.  We can look at a different way of implementing a workflow:

C#
static void AsWorkflow()
{
  ProcessWorkflow(Error, new Func<ErrorType>[] {
  GetConfig, 
  VersionChanged, 
  DownloadVersion, 
  Unzip, 
  CopyFiles, 
  RestartApp});
}

Which we could implement the workflow processor like this:

C#
static void ProcessWorkflow(Action<ErrorType> errorHandler, Func<ErrorType>[] workflow)
{
  ErrorType errorType = ErrorType.None;
  int idx = 0;

  while ( (errorType == ErrorType.None) && (idx <workflow.Length) )
  {
    errorType = errorType.If(workflow[idx++], errorHandler);
  }
}

This might be considered better because it returns immediately on failure, and the calling function makes it clearer that the array of functions are being called as a workflow, but is it better or are we just getting more and more obtuse? Again, if you even go down this path, that's an implementation decision you'll have to make.

So, there you have it -- some food for thought on working with workflows where each step potentially can exit with an error, and some different implementations, sticking with what can be done in procedural languages like C# that provide some functional programming capability.

History

  • 31st December, 2015: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
Questionnice article Pin
Manoj Kumar Choubey21-Sep-20 2:35
professionalManoj Kumar Choubey21-Sep-20 2:35 
SuggestionK.I.S.S. Pin
Member 1487978221-Jul-20 23:24
Member 1487978221-Jul-20 23:24 
QuestionUse of conditional logical AND Pin
Wendelius20-Jul-20 10:16
mentorWendelius20-Jul-20 10:16 
AnswerRe: Use of conditional logical AND Pin
Franc Morales20-Jul-20 11:01
Franc Morales20-Jul-20 11:01 
Suggestion+5 nice work, and some thoughts Pin
honey the codewitch20-Jul-20 7:04
mvahoney the codewitch20-Jul-20 7:04 
GeneralRe: +5 nice work, and some thoughts Pin
mldisibio21-Jul-20 9:15
mldisibio21-Jul-20 9:15 
Lately I've been applying the System.Threading.Tasks.Dataflow blocks to approximate such a state machine.
Let's say you have a TransformBlock<TIn, TOut> that starts with a configuration and converts it to a file download task. Then you have another that validates the download file and a third that unzips it. Each source block can LinkTo its target block conditionally, based on its exit state, or LinkTo an error handling block if the condition is not met.
The blocks are chained together creating a unit of work. But once a success condition is not met, the remaining blocks in the flow are bypassed.

One of the reasons I took this approach was precisely because I was looking for a more state based and fluent expression of complex if-then-else logic while encapsulating each unit of data processing in its own class but not losing sight of how all the units fit together to form a single workflow.
GeneralRe: +5 nice work, and some thoughts Pin
honey the codewitch21-Jul-20 9:29
mvahoney the codewitch21-Jul-20 9:29 
GeneralRe: +5 nice work, and some thoughts Pin
mldisibio21-Jul-20 10:07
mldisibio21-Jul-20 10:07 
QuestionI missed this first time round Pin
Garth J Lancaster20-Jul-20 3:23
professionalGarth J Lancaster20-Jul-20 3:23 
QuestionMy vote of 5 Pin
TheGreatAndPowerfulOz10-Jul-17 10:46
TheGreatAndPowerfulOz10-Jul-17 10:46 
QuestionHow to do this in C++? Pin
Stefan_Lang8-Jan-16 2:26
Stefan_Lang8-Jan-16 2:26 
QuestionAlternatives to if-then-else Pin
alan.cooper3-Jan-16 21:40
alan.cooper3-Jan-16 21:40 
AnswerRe: Alternatives to if-then-else Pin
Stefan_Lang10-Jan-16 22:18
Stefan_Lang10-Jan-16 22:18 
AnswerRe: Alternatives to if-then-else Pin
TheGreatAndPowerfulOz10-Jul-17 9:56
TheGreatAndPowerfulOz10-Jul-17 9:56 
AnswerRe: Alternatives to if-then-else Pin
dmjm-h22-Jul-20 11:06
dmjm-h22-Jul-20 11:06 
QuestionI've seen this nonsense before Pin
Member 30271203-Jan-16 5:07
Member 30271203-Jan-16 5:07 
AnswerRe: I've seen this nonsense before Pin
Greg Russell4-Jan-16 0:21
professionalGreg Russell4-Jan-16 0:21 
GeneralBeside all serious arguments ... Pin
LightTempler20-Jul-20 9:35
LightTempler20-Jul-20 9:35 
QuestionWhy not use exceptions? Pin
Member 78098722-Jan-16 2:11
Member 78098722-Jan-16 2:11 
AnswerRe: Why not use exceptions? Pin
Marc Clifton2-Jan-16 2:47
mvaMarc Clifton2-Jan-16 2:47 
GeneralRe: Why not use exceptions? Pin
Member 78098722-Jan-16 4:20
Member 78098722-Jan-16 4:20 
GeneralRe: Why not use exceptions? Pin
Stefan_Lang8-Jan-16 5:50
Stefan_Lang8-Jan-16 5:50 
GeneralRe: Why not use exceptions? Pin
Member 78098728-Jan-16 8:35
Member 78098728-Jan-16 8:35 
GeneralRe: Why not use exceptions? Pin
Stefan_Lang10-Jan-16 22:14
Stefan_Lang10-Jan-16 22:14 
GeneralRe: Why not use exceptions? Pin
Member 780987211-Jan-16 6:09
Member 780987211-Jan-16 6:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.