1

Suppose (entirely hypothetically ;) ) that I have nuget package, which essentially exposes a set of static extension methods:

public static class MyNugetLibrary
{
    public static int DoSomethingExpensiveAndUseful(this string input)
    {
        return input.Length;
    }

    public static int DoSomethingElseExpensiveAndUseful(this string input)
    {
        return (int)input.ToCharArray().First();
    }
}

And, for sane reasons, I conclude that what this package really needs is caching. The output is constant given the input, and the input is something primitive.

In my case there's no conceivable way for the output to change, so I never have to worry about cache invalidation etc.

If there were just 1 or 2 methods I could just add a private static Dictionary to the extension class, and in the method ask the dictionary for the answer.

But I'd quite like to not duplicate so much code, and there's a really nice memoize function:

public static Func<T, TResult> Memoize<T, TResult>(this Func<T, TResult> f)
{
    var cache = new ConcurrentDictionary<T, TResult>();
    return a => cache.GetOrAdd(a, f);
}

(Stolen from here: https://www.aleksandar.io/post/memoization/)

But I can't quite figure out how to use that method to make these functions memoized, without changing the external interface of my package.

How can I do this?


Massive bonus points available if we can further do this in such a way that the caching can be disabled by the end user (MyNugetLibrary.DisableCaching()) in case they are worried about, e.g. the memory footprint.

Brondahl
  • 7,402
  • 5
  • 45
  • 74
  • For those wishing to win infinite internet points, see also here: https://stackoverflow.com/questions/56269443/can-i-memoize-a-generic-method and figure out a way to achieve both of these in one solution :) – Brondahl May 23 '19 at 07:07
  • Ignoring the efficiency aspects of creating a dictionary instance for each memoization action, what does prevent you from using this method as it described in the article ? – Dmytro Mukalov May 23 '19 at 08:31

1 Answers1

0

You can use Fody/MethodCache

Run

Install-Package Fody
Install-Package MethodCache.Fody

Then you can change your methods to:

public interface ICache
{
    bool Contains(string key);
    T Retrieve<T>(string key);
    void Store(string key, object data);
    void Remove(string key);
}

public static class MyNugetLibrary
{
    public static ICache Cache { get; set; } = DefaultCache;

    public readonly static ICache DefaultCache = new MemoryCache();
    public readonly static ICache NoCache = new UnCache();
    [Cache]
    public static int DoSomethingExpensiveAndUseful(this string input)
    {
        return input.Length;
    }
    [Cache]
    public static int DoSomethingElseExpensiveAndUseful(this string input)
    {
        return (int)input.ToCharArray().First();
    }
}
Aron
  • 15,464
  • 3
  • 31
  • 64
  • Hmm, that looks like a really nice solution if I'm OK with adding a huge bit of complexity to the codebase (`Fody`). But I'd prefer a more explicitly coded approach, if one is available. If no other solutions are offered in a couple of days I'll accept this (feel free to ping me if I forget!). – Brondahl May 23 '19 at 07:16
  • @Brondahl There is ZERO complexity added to your code base by Fody. It is a Post Compile IL Weaving technology. – Aron May 23 '19 at 07:18
  • sorry, "Codebase" is perhaps the wrong term ... "Library" / "Package". Currently my entire package is a couple of dozen lines of code, which anyone who wants to use it can open up and read instantly, and understand exactly what and how it is working. Adding Fody to it is a massive step up in complexity of "what exactly am adding to my project" if someone cares deeply about minimising transitive dependencies – Brondahl May 23 '19 at 07:21
  • Fair enough. However, in general, your question is about Aspect Orientated Programming, and how to add the Memoize Aspect to your code with minimal impact. For more reading on the subject, [refer to my answer on the topic.](https://stackoverflow.com/a/25366644/1808494) – Aron May 23 '19 at 07:42