Recently, I’ve been focusing on the front-end side of building web applications with topics like React Hooks and Angular Signals. It’s a fascinating data model that can make working with data dependency graphs much more straightforward.

To my surprise, other folks in the .NET community have also been inspired by the work happening in the frontend space. While scanning NuGet, I found MemoizR, a library that takes inspiration from the frontend world to bring the concept of dynamic lazy memoization to .NET developers.

In this post, we’ll see a short example using the library and explain the sample output. Let’s go!

What is MemoizR?

According to the author, Timon Krebs, MemoizR is a declarative structured concurrency implementation for .NET that simplifies (and enhances) standard data flow methods across multiple threads. These methods include error handling, branching logic, and data mutation. Doing so helps developers manage concurrency more efficiently for simple to complex multi-thread scenarios.

Critical features of MemoizR include dynamic lazy memoization, which determines if values need to be reevaluated. Another feature is declarative structured concurrency, which makes building and handling scenarios more straightforward. Regarding scenarios, MemoizR also builds a dependency graph, which helps reduce unnecessary computations if a particular branch has not been affected by changes. These features also lead to automatic synchronization and improved overall performance.

Let’s look at an example application.

Hello MemoizR Sample

To start with MemoizR, you must install the latest version into a new Console project. As of this post, MemoizR is still in prerelease, so you should enable prerelease visibility in your NuGet tool window. You may also run the following command.

dotnet add package MemoizR --version 0.1.0-rc4

Once you install the dependency, paste the following code into the Program.cs file of the project.

using MemoizR;

var f = new MemoFactory();

var one = f.CreateSignal(2);
var two = f.CreateSignal(2);

var squareOne = f.CreateMemoizR(async () =>
{
    var value = await one.Get();
    Console.WriteLine($"Square One: {value}");
    return Math.Pow(value, 2);
});

var squareTwo = f.CreateMemoizR(async () =>
{
    var value = await two.Get();
    Console.WriteLine($"Square Two: {value}");
    return Math.Pow(value, 2);
});

var final = f.CreateMemoizR(async () =>
{
    var result = await squareOne.Get() + await squareTwo.Get();
    Console.WriteLine("Add Squares");
    return result;
});


while (true)
{
    Console.Write($"Set one (current: {await one.Get()}):");
    var user1 = Console.ReadLine();
    if (user1 is not null && int.TryParse(user1, out var value)) {
        await one.Set(value);
    }
    
    Console.Write($"Set two (current: {await two.Get()}):");
    var user2 = Console.ReadLine();
    if (user2 is not null && int.TryParse(user2, out value)) {
        await two.Set(value);
    }

    var result = await final.Get();
    Console.WriteLine($"Result is {result}.\n");
}

This sample aims to show the dependency graph in action by changing the values of one and two. We’ll execute the following scenarios from this app:

  1. Initial run
  2. Change the value of one and not two
  3. Change the value of two and not one
  4. Change no values.

We should see the following outputs reflecting which parts of the graph were executed.

Let’s start with the initial run’s output.

Set one (current: 2):
Set two (current: 2):
Square One: 2
Square Two: 2
Add Squares
Result is 8.

You can see all parts of our graph executed. Now, let’s only change the value of one. You’ll notice only half of the graph dependent on the one value is executed.

Set one (current: 2):3
Set two (current: 2):
Square One: 3
Add Squares
Result is 13.

Now let’s only change two. We should see the opposite is true.

Set one (current: 3):
Set two (current: 2):1
Square Two: 1
Add Squares
Result is 10.

Finally, let’s not change any values. Our graph should not execute any nodes, as it’s value is unchanged.

Set one (current: 3):
Set two (current: 1):
Result is 10.

Cool!

Conclusion

This library is still early but shows much promise for building complex multi-threaded scenarios where elements may or may not change. As you say in the sample, the code is easy to follow and the flow is easily modifiable. I think the author has something interesting here and I hope to see how they evolve it over time.

I hope you give MemoizR a try and let me know what you think. As always, thanks for reading and sharing my posts. Cheers :)