Logical Expressions in .NET

it_saigeDeveloper
CERTIFIED EXPERT
Published:
For many programmers something like:

if (someState != someOtherState && someState == someOtherOtherState || someState == someOtherOtherOtherState)
{
    ....
}

Is a common pattern. I am here to discuss alternatives to this pattern which will reduce *code smells* within code analyzers.
If you have ever written a program, there is a good chance that you have had to use expression logic in order to determine a decision path. Usually this expression logic comes in a few different common forms. This article will focus on expression logic use if/if...else/if...elseif type of statements:
if (someState != otherState & someState == otherOtherState | [...])
{
   // follow some logic path
}
else
{
   // follow some other path
}

// Or
if (someState != otherState && someState == otherOtherState || [...])
{
   // follow some logic path
}
else
{
   // follow some other path
}
What is the difference between these two statements?

The first one uses the binary logical operators (& and |) [And and Or for VB.NET developers]; while the second one uses the binary conditional logical operators (&& and ||) [AndAlso and OrElse for VB.NET developers].

What is this difference between these binary operators?

The binary logical operators are non-short-circuiting operators while the binary conditional logical operators are short-circuiting operators. What that means in layman's terms is when the logical expression runs into an expression criteria which fails the true/false test of the operation, the binary conditional logical operator will no longer continue to check for other expression passes/failures while the binary logical operator will continue to check the other expressions. Because of this, we tend to defer to the short-circuited path as it is more performant.

Enter the code analyzers, when you introduce a logical expression which exceeds a certain number of logical checks; e.g. normally three to four; or:
if (someState != someOtherState && someState == someOtherOtherState || someState == someOtherOtherOtherState || someState == someOtherOtherOtherOtherState)
{
   [...]
}
else
{
   [...]
}
The common resolution to this is to create a method which returns a Boolean value based upon the return of a number of logical expressions (again limited because of code analyzers); e.g.:
if (SomeStateMethod())
{
   [...]
}
else
{
   [...]
}

[...]
private boolean SomeStateMethod()
{
   return someState != someOtherState && someState == someOtherOtherState || someState == someOtherOtherOtherState || someState == someOtherOtherOtherOtherState;
}
All you are doing here is moving a group of expressions to be evaluated by a method rather than using the expressions in your if/if...else/if...elseif statement. The code analyzer will still ding your method if it breaches the logical expression contract, which then would cause you to create another method for more logical expressions, so forth and so on. Granted many of this can be resolved by creating less complex objects and comparison methods, etc.; but I digress.

So how can we break this cycle and produce code which is potentially more clear and concise? Enter LINQ.

LINQ (or Language INtegrated Query) introduces a couple of methods that assist with logical expression evaluation; the two methods I speak of are Any and All. Great, I'll just use those, you might say. Woah, wait a second cowboy.

Normally, a C# developer might use something like:
var expressions = new[]
{
   true,
   true,
   true,
   false,
   true
};


return expressions.All(x => x);
For VB.NET developers, the above would look like this:
Dim expressions = New()
{
   True,
   True,
   True,
   False,
   True
}

Return expressions.All(Function(x) x)
The problem with this is that Any or All will not short-circuit the execution. Any is the counter operative of the binary logical operator | [Or for VB.NET developers]; while All is the counter operative of the binary logical operator & [And for VB.NET developers].

So what can we do?

Enter Func; more specifically Func<bool> [Func(Of Boolean) for VB.NET developers]. With an IEnumerable of Func<bool> expressions, you can cause a short-circuit within the LINQ methods Any and/or All.  Let me demonstrate.

C# example:
var expressions = new Func<bool>[]
{
   () => { return true; },
   () => { return true; },
   () => { return true; },
   () => { return false; },
   () => { return true; }
};


return expressions.All(x => x.Invoke());
VB.NET example:
Dim expressions = New Func(Of Boolean)() _
{
   Function() True,
   Function() True,
   Function() True,
   Function() False,
   Function() True
}
Return expressions.All(Function(x) >= x.Invoke())
Granted it will take a few nanoseconds more than your standard logical expressions, but it will provide a short-circuit option where you have methods/logical expressions which run deep into an objects method/property path while retaining a cleaner and more concise logical expression series.

Proof of concept:

C# example:
using System;
using System.Linq;
using System.Runtime.CompilerServices;

namespace EE_A35779
{
    class Program
    {
        static void Main(string[] args)
        {
            NonShortCurcuitedNonLinq();
            Console.WriteLine();

            ShortCurcuitedNonLinq();
            Console.WriteLine();

            NonShortCircuitedLinq();
            Console.WriteLine();

            ShortCircuitedLinq();
            Console.ReadLine();
        }

        static bool NonShortCurcuitedNonLinq()
        {
            return LogAndReturn(false) & LogAndReturn(true) & LogAndReturn(true) & LogAndReturn(true) & LogAndReturn(true);
        }

        static bool ShortCurcuitedNonLinq()
        {
            return LogAndReturn(false) && LogAndReturn(true) && LogAndReturn(true) && LogAndReturn(true) && LogAndReturn(true);
        }

        static bool NonShortCircuitedLinq()
        {
            var expressions = new[]
            {
                LogAndReturn(false),
                LogAndReturn(true),
                LogAndReturn(true),
                LogAndReturn(true),
                LogAndReturn(true)
            };

            return expressions.All(x => x);
        }

        static bool ShortCircuitedLinq()
        {
            var expressions = new Func<bool>[]
            {
                () => { return LogAndReturn(false); },
                () => { return LogAndReturn(true); },
                () => { return LogAndReturn(true); },
                () => { return LogAndReturn(true); },
                () => { return LogAndReturn(true); }
            };

            return expressions.All(x => x.Invoke());
        }

        static bool LogAndReturn(bool value, [CallerMemberName] string method = "")
        {
            Console.WriteLine($"{method} - {value}");
            return value;
        }
    }
}

VB.NET example:
Imports System.Runtime.CompilerServices

Module Program
    Sub Main(args As String())
        NonShortCurcuitedNonLinq()
        Console.WriteLine()

        ShortCurcuitedNonLinq()
        Console.WriteLine()

        NonShortCircuitedLinq()
        Console.WriteLine()

        ShortCircuitedLinq()
        Console.ReadLine()
    End Sub

    Function NonShortCurcuitedNonLinq() As Boolean
        Return LogAndReturn(False) And LogAndReturn(True) And LogAndReturn(True) And LogAndReturn(True) And LogAndReturn(True)
    End Function

    Function ShortCurcuitedNonLinq() As Boolean
        Return LogAndReturn(False) AndAlso LogAndReturn(True) AndAlso LogAndReturn(True) AndAlso LogAndReturn(True) AndAlso LogAndReturn(True)
    End Function

    Function NonShortCircuitedLinq() As Boolean
        Dim expressions =
        {
            LogAndReturn(False),
            LogAndReturn(True),
            LogAndReturn(True),
            LogAndReturn(True),
            LogAndReturn(True)
        }

        Return expressions.All(Function(x) x)
    End Function

    Function ShortCircuitedLinq() As Boolean
        Dim expressions = New Func(Of Boolean)() _
        {
            Function() LogAndReturn(False),
            Function() LogAndReturn(True),
            Function() LogAndReturn(True),
            Function() LogAndReturn(True),
            Function() LogAndReturn(True)
        }

        Return expressions.All(Function(x) x.Invoke())
    End Function

    Function LogAndReturn(ByVal value As Boolean, <CallerMemberName> ByVal Optional method As String = "")
        Console.WriteLine($"{method} - {value}")
        Return value
    End Function
End Module

Each produce the following output:

Please leave any comments below and happy coding.
1
427 Views
it_saigeDeveloper
CERTIFIED EXPERT

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.