Archive for the ‘Binary Tree’ Category

The Results

By:  Cole Francis, Senior Solution Architect, The PSC Group, LLC

Let’s just say that I had a situation where I was calling a third-party API to return valuable information, but that third-party service occasionally failed.  What I discovered through recursion is that 1 out of every 3-5 calls succeeded, but it wasn’t guaranteed.  Therefore, I couldn’t simply wrap my core logic in a hard-coded iterative loop and expect it to succeed.  So, I was relegated to either coming up with a way to write some custom Retry logic to handle errors and reattempt the call or locating and existing third-party package that offers this sort of functionality as not to reinvent the proverbial wheel.

Fortunately, I stumbled across a .NET NuGet Package called Polly.   After reading the abstract about the offering (Click Here to Read More About the Polly Project), I discovered that Polly is a .NET compatible library that complies with transient-fault-handling logic by implementing policies that offer thread-safe resiliency to Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback logic, and in a way that is very easy to implement inside a .NET project codebase.  I also need to point out that Polly targets .NET 4.0, .NET 4.5 and .NET Standard 1.1.

While Polly offers a plethora of capabilities, many of which I’ll keep in my back pocket for a rainy day, I was interested in just one, the Retry logic.  Here’s how I implemented it.  First, I included the Polly NuGet Package in my solution, like so:

The Results

Next, I included the following lines of code when calling the suspect third-party Web API:

// Here is my wait and retry policy, with 250 millisecond wait intervals.
// It will attempt to call the API 10 times.
Policy
  .Handle(e => (e is Exception))
  .WaitAndRetry(10, attempt => TimeSpan.FromMilliseconds(250))
  .Execute(() =>
  {
    // Your core logic should go here!  
    // If an exception is thrown by the called object, 
    // then Polly will wait 250ms and try again for a total of 10 times.
    var response = CallToSuspectThirdPartyAPI(input);
  });

That’s all there really is to it, and I’m only scratching the surface when it comes to Polly’s full gamut of functionality.  Here’s a full list of Polly’s capabilities if you’re interested:

  1. Retry – I just described this one to you.
  2. Circuit Breaker – Fail fast under struggling conditions (you define the conditions and thresholds).
  3. Timeout – Wait until you hit a certain point, and then move on.
  4. Bulkhead Isolation – Provides fault isolation, so that certain failing threads don’t fault the entire process.
  5. Cache – Provides caching (temporary storage and retrieval) capabilities.
  6. Fallback – Anticipates a potential failure and allows a developer to provide an alternative course of action if a potential failure is ever realized.
  7. PolicyWrap – Allows for any (and all) of the previously mentioned policies to be combined, so that different programmatic strategies can be exercised when different faults occur.

Thanks for reading, and keep on coding!  🙂

Advertisements

CrossProcessMemoryMaps

Author: Cole Francis, Architect

BACKGROUND PROBLEM

Many moons ago, I was approached by a client about the possibility of injecting a COM wrapped .NET assembly between two configurable COM objects that communicated with one another. The basic idea was that a hardware peripheral would make a request through one COM object, and then that request would be would intercepted by my new COM object which would then prioritize a hardware object’s data in a cross-process, global singleton. From there, any request initiated by a peripheral would then be reordered using the values persisted in my object.

Unfortunately, the solution became infinitely more complex when I learned that peripheral requests could originate from software running on different processes on the same machine. My first attempt involved building an out-of-process storage cache used to update and retrieve data as needed. Although it all seemed perfectly logical, it lacked the split-second processing speed that the client was looking for. So, next I tried to reading and writing data to shared files on the local file system. This also worked but lacked split-second processing capabilities. As a result, I ended up going back and forth to the drawing board before finally implementing a global singleton COM object that met client’s needs (Yes, I know it’s an anti-pattern…but it worked!).

Needless to say, the outcome was a rather bulky solution, as the intermediate layer of software I wrote had to play nicely with COM objects that it was never intended to live between, as well as adhere to specific IDispatch interfaces that weren’t very well documented, and it reacted to functionality that at times seemed random. Although the effort was considered highly successful, development was also very tedious and came at a price…namely my sanity. Looking back on everything well over a decade later and applying the knowledge that I possess today, I definitely would have implemented a much more elegant solution using an API stack that I’ll go over in just a minute.

As for now, let’s switch gears and discuss a something that probably seems completely unrelated to the topic at hand, and that is memory functions. Yes, that’s right…I said memory functions. It’s my belief that when most developers think of storing object and data in memory, two memory functions immediately come to their mind, namely the Heap and Virtual memory (explained below). While these are great mechanisms for managing objects and data internal to a single process, neither of the aforementioned memory-based storage facilities can be leveraged across multiple processes without employing some sort of out-of-process mechanism to persist and share the data.

    1) Heap Memory Functions: Represent instances of a class or an array. This type of memory isn’t immediately returned when a method completes its scope of execution. Instead, Heap memory is reclaimed whenever the .NET garbage collector decides that the object is no longer needed.

    2) Virtual Memory Functions: Represent value types, also known as primitives, which reside in the Stack. Any memory allocated to virtual memory will be immediately returned whenever the method’s scope of execution completes. Using the Stack is obviously more efficient than using the Heap, but the limited lifetime of value types makes them implausible candidates to share data between different classes…let alone sharing data between different processes.

BRING ON MEMORY MAPPING

While most developers focus predominantly on managing Heap and Virtual memory within their applications, there are also a few other memory options out there that are sometimes go unrecognized, including “Local, Global Memory”, “CRT Memory”, “NT Virtual Memory”, and finally “Memory-Mapped Files”. Due to the nature of our subject matter, this article will concentrate solely on “Memory-Mapped Files” (highlighted in orange in the pictorial below).

MemoryMappedFiles

To break it down into layman’s terms, a memory-mapped file allows you to reserve an address space and then commit physical storage to that region. In turn, the physical storage stems from a file that is already on the disk instead of the memory manager, which offers two notable advantages:

    1) Advantage #1 – Accessing data files on the hard drive without taking the I/O performance hit due to the buffering of the file’s content, making it ideal to use with large data files.

    2) Advantage #2 – Memory-mapped files provide the ability to share the same data with multiple processes running on the same machine.

Make no mistake about it, memory-mapped files are the most efficient way for multiple processes running on a single machine to communicate with one another! In fact, they are often used as process loaders in many commonly used operating systems today, including Microsoft Windows. Basically, whenever a process is started the operating system accesses a memory-mapped file in order to execute the application. Anyway, now that you know this little tidbit of information, you should also know that there are two types of memory-mapped files, including:

    1) Persisted memory-mapped files: After a process is done working on a piece of data, that mapped file can then be named and persisted to a file on the hard drive where it can be shared between multiple processes. These files are extremely suitable for working with large amounts of data.

    2) Non-persisted memory-mapped files: These are files that can be shared between two or more disparate threads operating within a single process. However, they don’t get persisted to the hard drive, which means that their data cannot be accessed by other processes.

I’ve put together a working example that showcases the capabilities of persisted memory-mapped files for demonstration purposes. As a precursor, the example depicts mutually exclusive thoughts conjured up by the left and right halves of the brain. Each thought lives and gets processed using its own thread, which in turn gets routed to the cerebral cortex for thought processing. Inside the cerebral cortex, short-term and long-term thoughts get stored and retrieved in a memory-mapped file that’s availability is managed by a mutex.

    A mutex is an object that allows multiple program threads to synchronously share the same resource, such as file access. A mutex can be created with a name to leverage persisted memory-mapped files, or the mutex can be left unnamed to utilize non-persisted memory-mapped files.

In addition to this, I’ve also assembled another application that runs as a completely different process on the same physical machine but is still able to read and write to the persisted memory-mapped file created by the first application. So, let’s get started!

APPLYING VIRTUAL MEMORY TO HUMAN MEMORY

In the code below, I’ve created a console application that references two objects in Heap memory, and they are TheLeftBrain.Stimuli() and TheRightBrain.Stiuli(). I’ve accounted for asynchronous thought processes stemming from the left and right halves of the brain by employing an asynchronous LINQ operation that kicks off two asynchronously blocking sub-threads (i.e. One for the left half of the brain and the other for the right half of the brain). Once the sub-threads are kicked off, the primary thread blocks any further operations until the sub-threads complete their operations and return (or optionally error out). I’ve highlighted the code in orange where I’m performing the asynchronous LINQ operation):


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using LeftBrain;
using RightBrain;

namespace LeftBrainRightBrain
{
    class Program
    {
        /// 
        /// The main entry point into the application
        /// 
        /// 
        static void Main(string[] args)
        {
            // Performs an asynchronous operation on both the left and right halves of the brain
            try
            {
                LeftBrain.Stimuli leftBrainStimuli = new LeftBrain.Stimuli();
                RightBrain.Stimuli rightBrainStimuli = new RightBrain.Stimuli();

                // Invoke a blocking, parallel process
                Parallel.Invoke(() =>
                {
                    leftBrainStimuli.Think();
                }, () =>
                {
                    rightBrainStimuli.Think();
                });

                Console.ReadKey();
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}

At this point, each asynchronous sub-thread calls its respective Stimuli() class. It should be obvious that both the LeftBrain() and RightBrain() objects are fundamentally similar in nature and therefore share interfaces and inherit from the same base class object, with the only significant differences being the types of thoughts they invoke and the additional millisecond I added to Sleep() invocation to the RightBrain() class to simply show some variance between the manner in which the threads are able to process.

Nevertheless, each thought lives in its own isolated thread (making them sub-sub threads) that passes information along to the Cerebral Cortex for data committal and retrieval. Here is an example of the LeftBrain() class and its thoughts:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using CerebralCortex;

namespace LeftBrain 
{
   /// 
    /// Stimulations from the right half of the brain
    /// 
    /// 
    public class Stimuli : Memory, IStimuli
    {
        /// 
        /// Stores memories in a list
        /// 
        /// 
        private List memories = new List();

        /// 
        /// An overloaded constructor
        /// 
        /// 
        public void Think()
        {
            try
            {
                string threadName = string.Empty;
                int threadCounter = 0;

                // Add a list of left brain memories
                memories.Add("The area of a circle is π r squared.");
                memories.Add("The Law of Inertia is Isaac Newton's first law.");
                memories.Add("Richard Feynman was a physicist known for his theories on quantum mechanics.");
                memories.Add("y = mx + b is the equation of a Line, standard form and point-slope.");
                memories.Add("A hypotenuse is the longest side of a right triangle.");
                memories.Add("A chord represents a line segment within a circle that touches 2 points on the circle.");
                memories.Add("Max Planck's quantum mechanical theory suggests that each energy element is proportional to its frequency.");
                memories.Add("A geometry proof is a written account of the complete thought process that is used to reach a conclusion");
                memories.Add("Pythagorean theorem is a relation in Euclidean geometry among the three sides of a right triangle.");
                memories.Add("A proof of Descartes' Rule for polynomials of arbitrary degree can be carried out by induction.");

                // Recount your memories
                memories.ForEach(memory =>
                {
                    this.ProcessThought(string.Format("Thread: {0} (Left Brain)", threadCounter += 1), memory);
                });
            }
            catch (Exception)
            {
                
                throw;
            }
        }

        /// 
        /// Controls the thought process for this half of the brain
        /// 
        /// 
        public void ProcessThought(string threadName, string memory)
        {
            try
            {
                Thread.Sleep(3000);
                Thread monitorThread = null;

                // Spin up a new thread delegate to invoke the thought process
                monitorThread = new Thread(delegate()
                {
                    base.InvokeThoughtProcess(threadName, memory);
                });

                // Name the thread and start it
                monitorThread.Name = threadName;
                monitorThread.Start();
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}

Likewise, shown below is an example of the RightBrain() class and its thoughts. Once again, the RightBrain() differs from the LeftBrain() mainly in terms of the types thoughts that get invoked, with the left half of the brain’s thoughts being more cognitive in nature and the right half of the brain’s thoughts being more artistic:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using CerebralCortex;

namespace RightBrain
{
    /// 
    /// Stimulations from the right half of the brain
    /// 
    /// 
    public class Stimuli : Memory, IStimuli
    {
        /// 
        /// Stores memories in a list
        /// 
        /// 
        private List memories = new List();

        /// 
        /// An overloaded constructor
        /// 
        /// 
        public void Think()
        {
            try
            {
                string threadName = string.Empty;
                int threadCounter = 0;

                // Add a list of right brain memories
                memories.Add("I wonder if there's a Van Gough Documentary on Television?");
                memories.Add("Isn't the color blue simply radical.");
                memories.Add("Why don't you just drop everything and hitch a ride to California, dude?");
                memories.Add("Wouldn't it be cool to be a shark?");
                memories.Add("This World really is my oyster.  Now, if only I had some cocktail sauce...");
                memories.Add("Why did I stop finger painting?");
                memories.Add("Does anyone want to go to a BBQ?");
                memories.Add("Earth tones are the best.");
                memories.Add("Heavy metal bands rock!");
                memories.Add("I like really shiny thingys.  Oh, Look!  A shiny thingy...");

                // Recount your memories
                memories.ForEach(memory =>
                {
                    this.ProcessThought(string.Format("Thread: {0} (Right Brain)", threadCounter += 1), memory);
                });
            }
            catch (Exception)
            {
                
                throw;
            }
        }

        /// 
        /// Controls the thought process for this half of the brain
        /// 
        /// 
        public void ProcessThought(string threadName, string memory)
        {
            try
            {
                Thread.Sleep(4000);
                Thread monitorThread = null;

                // Spin up a new thread delegate to invoke the thought process
                monitorThread = new Thread(delegate()
                {
                    base.InvokeThoughtProcess(threadName, memory);
                });

                // Name the thread and start it
                monitorThread.Name = threadName;
                monitorThread.Start();
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}

Regardless, the thread delegates spawned in the LeftBrain and RightBrain Stimuli() classes are responsible for contributing to short-term memory, as each thread commits its discrete memory item to a growing list of memories via the Thought() object. Each thread is also responsible for negotiating with the local mutex (highlighted below in orange) in order to access the critical sections of the code where thread-safety becomes absolutely imperative (highlighted below in wheat), as the individual threads add their messages to the global memory-mapped file (highlighted below in silver).

After each thread writes its memory to the memory-mapped file in the critical section of the code, it then releases the mutex (highlighted below in green) and allows the next sequential thread to lock the mutex and safely enter into the critical section of the code. This behavior repeats itself until all of the threads have exhausted their discrete units-of-work and safely rejoin the hive in their respective hemispheres of the brain. Once all processing completes, the block is then lifted by the primary thread and normal processing continues.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Xml.Serialization;

namespace CerebralCortex
{
    ///    
    /// The common area of the brain that controls thought
    /// 
    /// 
    public class Memory
    {
        /// 
        /// The local and global mutexes
        /// 
        /// 
        Mutex localMutex = new Mutex(false, "CCFMutex");
        MemoryMappedFile memoryMap = null;

        /// 
        /// Shared memory between the left and right halves of the brain
        /// 
        /// 
        static List<string> Thoughts = new List<string>();

        /// 
        /// Stores a thought in memory
        /// 
        /// 
        private bool StoreThought(string threadName, string thought)
        {
            bool retVal = false;

            try
            {
                Thoughts.Add(string.Concat(threadName, " says: ", thought));
                retVal = true;
            }
            catch (Exception)
            {
                
                throw;
            }

            return retVal;
        }

        /// 
        /// Retrieves a thought from memory
        /// 
        /// 
        private string RetrieveFromShortTermMemory()
        {
            try
            {
                // Returns the last stored thought (simulates short-term memory)
                return Thoughts.Last();
            }
            catch (Exception)
            {
                
                throw;
            }
        }

        /// 
        /// Invokes the thought process (uses a local mutex to control thread access inside the same process)
        /// 
        /// 
        public bool InvokeThoughtProcess(string threadName, string thought)
        {
            try
            {
                // *** CRITICAL SECTION REQUIRING THREAD-SAFE OPERATIONS ***
                {

                    // Causes the thread to wait until the previous thread releases
                    localMutex.WaitOne();

                    // Store the thought
                    StoreThought(threadName, thought);

                    // Create or open the cross-process capable memory map and write data to it
                    memoryMap = MemoryMappedFile.CreateOrOpen("CCFMemoryMap", 2000);

                    byte[] Buffer = ASCIIEncoding.ASCII.GetBytes(string.Join("|", Thoughts));
                    MemoryMappedViewAccessor accessor = memoryMap.CreateViewAccessor();
                    accessor.Write(54, (ushort)Buffer.Length);
                    accessor.WriteArray(54 + 2, Buffer, 0, Buffer.Length);

                    // Conjures the thought back up
                    Console.WriteLine(RetrieveFromShortTermMemory());
                }
                return true;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                // Releases the lock on the critical section of the code
                localMutex.ReleaseMutex();
            }

            return false;
        }
    }
}

With the major portions of the code complete, I am now able to run the application and watch the threads add their memories to the list of memories in the memory-mapped file via the critical section of the cerebral cortex code (click on the pictorial below to view the results)…

MemoryMapOutput

So, to quickly wrap this article up, my final step is to create a separate console application that will run as a completely separate process on the same physical machine in order to demonstrate the cross-process capabilities of a memory-mapped file. In this case, I’ve appropriately named my console application “OmniscientProcess”.

This application will make a call to the RetrieveLongTermMemory() method in its same class in order to negotiate with the global mutex. Provided the negotiation process goes well, the “OmniscientProcess” will attempt to retrieve the data being preserved within the memory-mapped file that was created by our previous application. In theory, this example is equivalent to having some external entity (i.e. someone or something) tapping into your own personal thoughts.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using CerebralCortex;

namespace OmniscientProcess
{
    class Program
    {
        static Mutex globalMutex = new Mutex(false, "CCFMutex");
        static MemoryMappedFile memoryMap = null;

        static void Main(string[] args)
        {
            // Reference the memory object and retrieve our memory-mapped data
            CerebralCortex.Memory cerebralMemory = new Memory();
            List<string> longTermMemories = cerebralMemory.RetrieveLongTermMemory();

            longTermMemories.ForEach(memory =>
            {
                Console.WriteLine(memory);
            });

            Console.WriteLine(string.Empty);
            Console.WriteLine("Press any key to end...");
            Console.ReadKey();
        }

        /// 
        /// Retrieves all thoughts from memory (uses a global mutex to control thread access from different processes)
        /// 
        /// 
        private static List<string> RetrieveLongTermMemory()
        {
            try
            {
                // Causes the thread to wait until the previous thread releases
                globalMutex.WaitOne();

                string delimitedString = string.Empty;

                memoryMap = MemoryMappedFile.OpenExisting("CCFMemoryMap", MemoryMappedFileRights.FullControl);

                MemoryMappedViewAccessor accessor = memoryMap.CreateViewAccessor();
                ushort Size = accessor.ReadUInt16(54);
                byte[] Buffer = new byte[Size];
                accessor.ReadArray(54 + 2, Buffer, 0, Buffer.Length);
                string delimitedThoughts = ASCIIEncoding.ASCII.GetString(Buffer);
                return delimitedThoughts.Split('|').ToList();
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                // Releases the lock on the critical section of the code
                globalMutex.ReleaseMutex();
            }
        }
    }
}

The aforementioned application has the ability to retrieve the state of the memory-mapped file from an external process at any point in time, except of course when the mutex is locked. It’s the responsibility of the mutex to exercise thread safety, regardless of the originating process, whenever a thread attempts to access the shared address space that comprises the memory-mapped file (see below):

Output 1 – Here’s a partial listing that was retrieved early in the process:
MemoryMapRetrieval1

Output 2 – Here’s the full listing that was retrieved after all of the threads committed their data:
OmniscientProcess

Finally, while memory-mapped files certainly aren’t a new concept (they’ve actually been around for decades), they are sometimes difficult to wrap your head around when there’s a sizable number of processes and threads flying around in the code. And, while my examples aren’t necessarily basic ones, hopefully they employ some rudimentary concepts that everyone is able to quickly and easily understand.

To recount my steps, I demonstrated calls to disparate objects getting kicked off asynchronously, which in turn conjure up a respectable number of threads per object. Each individual thread, operating in each asynchronously executing object, goes to work by negotiating with a common mutex in an attempt to commit its respective data values to the cross-process, memory-mapped file that’s accessible to applications running as entirely different processes on the same physical machine.

Thanks for reading and keep on coding! 🙂

ColeFrancisBizRulesEngine

Author: Cole Francis, Architect

BACKGROUND

Over the past couple of days, I’ve pondered the possibility of creating a dynamic business rules engine, meaning one that’s rules and types are conjured up and reconciled at runtime. After reading different articles on the subject matter, my focus was imparted to the Microsoft Dynamic Language Runtime (DLR) and Labmda-based Expression Trees, which represent the factory methods available in the System.Linq.Expressions namespace and can be used to construct, query and validate relationally-structured dynamic LINQ lists at runtime using the IQueryable interface. In a nutshell, the C# (or optionally VB) compiler allows you to construct a list of binary expressions at runtime, and then it compiles and assigns them to a Lambda Tree data structure. Once assigned, you can navigate an object through the tree in order to determine whether or not that object’s data meets your business rule criteria.

AFTER SOME RESEARCHING

After reviewing a number of code samples offered by developers who have graciously shared their work on the Internet, I simply couldn’t find one that met my needs. Most of them were either too strongly-typed, too tightly coupled or applicable only to the immediate problem at hand. Instead, what I sought was something a little more reusable and generic. So, in absence of a viable solution, I took a little bit of time out of my schedule to create a truly generic prototype of one. This will be the focus of the solution, below.

THE SOLUTION

To kick things off, I’ve created a Expression Trees compiler that accepts a generic type as an input parameter, along with a list of dynamic rules. Its job is to pre-compile the generic type and dynamic rules into a tree of dynamic, IQueryable Lambda expressions that can validate values in a generic list at runtime. As with all of my examples, I’ve hardcoded the data for my own convenience, but the rules and data can easily originate from a data backing store (e.g. a database, a file, memory, etc…). Regardless, shown in the code block below is the PrecompiledRules Class, the heart of my Expression Trees Rules Engine, and for your convenience I’ve highlighted the line of code that performs the actual Expression Tree compilation in blue):


using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ExpressionTreesRulesEngine.Entities;

namespace ExpressionTreesRulesEngine
{
    /// Author: Cole Francis, Architect
    /// The pre-compiled rules type
    /// 
    public class PrecompiledRules
    {
        ///
        /// A method used to precompile rules for a provided type
        /// 
        public static List<Func<T, bool>> CompileRule<T>(List<T> targetEntity, List<Rule> rules)
        {
            var compiledRules = new List<Func<T, bool>>();

            // Loop through the rules and compile them against the properties of the supplied shallow object 
            rules.ForEach(rule =>
            {
                var genericType = Expression.Parameter(typeof(T));
                var key = MemberExpression.Property(genericType, rule.ComparisonPredicate);
                var propertyType = typeof(T).GetProperty(rule.ComparisonPredicate).PropertyType;
                var value = Expression.Constant(Convert.ChangeType(rule.ComparisonValue, propertyType));
                var binaryExpression = Expression.MakeBinary(rule.ComparisonOperator, key, value);

                compiledRules.Add(Expression.Lambda<Func>(binaryExpression, genericType).Compile());
            });

            // Return the compiled rules to the caller
            return compiledRules;
        }
    }
}


As you can see from the code above, the only dependency in my Expression Trees Rules Engine is on the Rule Class itself. Naturally, I could augment the Rule Class to the PreCompiledRules Class and eliminate the Rule Class altogether, thereby eliminating all dependencies. However, I won’t bother with this for the purpose of this demonstration. But, just know that the possibility does exist. Nonetheless, shown below is the concrete Rule class:


using System;
using System.Linq.Expressions;

namespace ExpressionTreesRulesEngine.Entities
{
    ///
    /// The Rule type
    /// 
    public class Rule
    {
        ///
        /// Denotes the rules predictate (e.g. Name); comparison operator(e.g. ExpressionType.GreaterThan); value (e.g. "Cole")
        /// 
        public string ComparisonPredicate { get; set; }
        public ExpressionType ComparisonOperator { get; set; }
        public string ComparisonValue { get; set; }

        /// 
        /// The rule method that 
        /// 
        public Rule(string comparisonPredicate, ExpressionType comparisonOperator, string comparisonValue)
        {
            ComparisonPredicate = comparisonPredicate;
            ComparisonOperator = comparisonOperator;
            ComparisonValue = comparisonValue;
        }
    }
}


Additionally, I’ve constructed a Car class as a test class that I’ll eventually hydrate with data and then inject into the compiled Expression Tree object for various rules validations:


using System;
using ExpressionTreesRulesEngine.Interfaces;

namespace ExpressionTreesRulesEngine.Entities
{
    public class Car : ICar
    {
        public int Year { get; set; }
        public string Make { get; set; }
        public string Model { get; set; }
    }
}


Next, I’ve created a simple console application and added a project reference to the ExpressionTreesRulesEngine project. Afterwards, I’ve included the following lines of code (see the code blocks below, paying specific attention to the lines of code highlighted in orange) in the Main() in order to construct a list of dynamic rules. Again, these are rules that can be conjured up from a data backing store at runtime. Also, I’m using the ICar interface that I created in the code block above to compile my rules against.

As you can also see, I’m leveraging the out-of-box LINQ.ExpressionTypes enumerates to drive my conditional operators, which is in part what allows me to make the PreCompiledRules class so generic. Never fear, the LINQ.ExpressionTypes enumeration contains a plethora of node operations and conditional operators (and more)…far more enumerates than I’ll probably ever use in my lifetime.


List<Rule> rules = new List<Rule> 
{
     // Create some rules using LINQ.ExpressionTypes for the comparison operators
     new Rule ( "Year", ExpressionType.GreaterThan, "2012"),
     new Rule ( "Make", ExpressionType.Equal, "El Diablo"),
     new Rule ( "Model", ExpressionType.Equal, "Torch" )
};

var compiledMakeModelYearRules= PrecompiledRules.CompileRule(new List<ICar>(), rules);


Once I’ve compiled my rules, then I can simply tuck them away somewhere until I need them. For example, if I store my compiled rules in an out-of-process memory cache, then I can theoretically store them for the lifetime of the cache and invoke them whenever I need them to perform their magic. What’s more, because they’re compiled Lambda Expression Trees, they should be lightning quick against large lists of data. Other than pretending that the Car data isn’t hardcoded in the code example below, here’s how I would otherwise invoke the functionality of the rules engine:


// Create a list to house your test cars
List cars = new List();

// Create a car that's year and model fail the rules validations      
Car car1_Bad = new Car { 
    Year = 2011,
    Make = "El Diablo",
    Model = "Torche"
};
            
// Create a car that meets all the conditions of the rules validations
Car car2_Good = new Car
{
    Year = 2015,
    Make = "El Diablo",
    Model = "Torch"
};

// Add your cars to the list
cars.Add(car1_Bad);
cars.Add(car2_Good);

// Iterate through your list of cars to see which ones meet the rules vs. the ones that don't
cars.ForEach(car => {
    if (compiledMakeModelYearRules.TakeWhile(rule => rule(car)).Count() > 0)
    {
        Console.WriteLine(string.Concat("Car model: ", car.Model, " Passed the compiled rules engine check!"));
    }
    else
    {
        Console.WriteLine(string.Concat("Car model: ", car.Model, " Failed the compiled rules engine check!"));
    }
});

Console.WriteLine(string.Empty);
Console.WriteLine("Press any key to end...");
Console.ReadKey();


As expected, the end result is that car1_Bad fails the rule validations, because its year and model fall outside the range of acceptable values (e.g. 2011 < 2012 and 'Torche' != 'Torch'). In turn, car2_Good passes all of the rule validations as evidenced in the pic below:

TreeExpressionsResults

Well, that’s it. Granted, I can obviously improve on the abovementioned application by building better optics around the precise conditions that cause business rule failures, but that exceeds the intended scope of my article…at least for now. The real takeaway is that I can shift from validating the property values on a list of cars to validating some other object or invoking some other rule set based upon dynamic conditions at runtime, and because we’re using compiled Lambda Expression Trees, rule validations should be quick. I really hope you enjoyed this article. Thanks for reading and keep on coding! 🙂