{"id":15410945,"url":"https://github.com/oskardudycz/memoization","last_synced_at":"2025-07-01T12:05:27.374Z","repository":{"id":79310812,"uuid":"341306334","full_name":"oskardudycz/Memoization","owner":"oskardudycz","description":"Memoization is a useful technique that allows easily optimize method calls. The sample shows how the Memoization works and how to implement it in C#. ","archived":false,"fork":false,"pushed_at":"2021-05-09T08:11:09.000Z","size":30,"stargazers_count":6,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-09T12:38:05.077Z","etag":null,"topics":["csharp","memoization"],"latest_commit_sha":null,"homepage":"https://event-driven.io/en/memoization_a_useful_pattern_for_quick_optimisation","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oskardudycz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["oskardudycz"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2021-02-22T19:01:35.000Z","updated_at":"2024-12-18T18:46:22.000Z","dependencies_parsed_at":"2023-05-25T17:15:36.759Z","dependency_job_id":null,"html_url":"https://github.com/oskardudycz/Memoization","commit_stats":{"total_commits":12,"total_committers":1,"mean_commits":12.0,"dds":0.0,"last_synced_commit":"97f85d9e093f72e68abf1ea68a5411daa736c3a5"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/oskardudycz/Memoization","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskardudycz%2FMemoization","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskardudycz%2FMemoization/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskardudycz%2FMemoization/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskardudycz%2FMemoization/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oskardudycz","download_url":"https://codeload.github.com/oskardudycz/Memoization/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskardudycz%2FMemoization/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262959559,"owners_count":23391057,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","memoization"],"created_at":"2024-10-01T16:47:09.359Z","updated_at":"2025-07-01T12:05:27.352Z","avatar_url":"https://github.com/oskardudycz.png","language":"C#","funding_links":["https://github.com/sponsors/oskardudycz","https://img.shields.io/static/v1?label=Sponsor\u0026message=%E2%9D%A4\u0026logo=GitHub\u0026link=https://github.com/sponsors/oskardudycz/","https://github.com/sponsors/oskardudycz/"],"categories":[],"sub_categories":[],"readme":"![Twitter Follow](https://img.shields.io/twitter/follow/oskar_at_net?style=social) [![Github Sponsors](https://img.shields.io/static/v1?label=Sponsor\u0026message=%E2%9D%A4\u0026logo=GitHub\u0026link=https://github.com/sponsors/oskardudycz/)](https://github.com/sponsors/oskardudycz/) [![blog](https://img.shields.io/badge/blog-event--driven.io-brightgreen)](https://event-driven.io/)\r\n\r\n# Memoization\r\n\r\nMemoization is a useful technique that allows easily optimize method calls. The sample shows how the memoization works and how to implement it in C#.\r\n\r\nIt takes a function and wraps it in the method to check if the provided input function was already called. If yes, then it will return value from the cache. If not - it'll return the cached value without running the function.\r\n\r\nThis works best for the method that are called numerous times. Memoized function should provide deterministic results. For the same input, it should return the same result and do not create side-effects (is a _[\"Higher-order function\"](https://en.wikipedia.org/wiki/Higher-order_function)_).\r\n\r\n```csharp\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\n\r\nnamespace Memoization\r\n{\r\n    public static class Memoizer\r\n    {\r\n        /// \u003csummary\u003e\r\n        /// Memoizes provided function. Function should provide deterministic results.\r\n        /// For the same input it should return the same result.\r\n        /// Memoized function for the specific input will be called once, further calls will use cache.\r\n        /// \u003c/summary\u003e\r\n        /// \u003cparam name=\"func\"\u003efunction to be memoized\u003c/param\u003e\r\n        /// \u003ctypeparam name=\"TInput\"\u003eType of the function input value\u003c/typeparam\u003e\r\n        /// \u003ctypeparam name=\"TResult\"\u003eType of the function result\u003c/typeparam\u003e\r\n        /// \u003creturns\u003e\u003c/returns\u003e\r\n        public static Func\u003cTInput, TResult\u003e Memoize\u003cTInput, TResult\u003e(this Func\u003cTInput, TResult\u003e func)\r\n        {\r\n            // create cache (\"memo\")\r\n            var memo = new Dictionary\u003cTInput, TResult\u003e();\r\n\r\n            // wrap provided function with cache handling\r\n            return input =\u003e\r\n            {\r\n                // check if result for set input was already cached\r\n                if (memo.TryGetValue(input, out var fromMemo))\r\n                    // if yes, return value\r\n                    return fromMemo;\r\n\r\n                // if no, call function\r\n                var result = func(input);\r\n                \r\n                // cache the result\r\n                memo.Add(input, result);\r\n\r\n                // return result\r\n                return result;\r\n            };\r\n        }\r\n    }\r\n\t\r\n    class Program\r\n    {\r\n        static void Main(string[] args)\r\n        {\r\n            Func\u003cType, Type, bool\u003e hasAttribute =\r\n                (type, attributeType) =\u003e type.GetCustomAttributes(attributeType, true).Any();\r\n\r\n            Func\u003cType, bool\u003e hasSomeCustomAttribute = \r\n                type =\u003e hasAttribute(type, typeof(SomeCustomAttribute));\r\n            \r\n            Func\u003cType, bool\u003e hasSomeCustomAttributeMemo = hasSomeCustomAttribute.Memoize();\r\n            \r\n            // Function will be called, as result wasn't memoized\r\n            hasSomeCustomAttributeMemo(typeof(FirstTypeWithAttribute));\r\n            // Function will be called, as this is not memoized function\r\n            hasSomeCustomAttribute(typeof(FirstTypeWithAttribute));\r\n            // Function will NOT be called, as result was memoized\r\n            hasSomeCustomAttributeMemo(typeof(FirstTypeWithAttribute));\r\n            \r\n            // Function will be called, as we memoized result of the different input value (other attribute)\r\n            hasSomeCustomAttributeMemo(typeof(SecondTypeWithAttribute));\r\n            // Function will be called, as this is not memoized function\r\n            hasSomeCustomAttribute(typeof(SecondTypeWithAttribute));\r\n            // Function will NOT be called, as result was memoized\r\n            hasSomeCustomAttributeMemo(typeof(SecondTypeWithAttribute));\r\n        }\r\n    }\r\n\r\n    [SomeCustomAttribute]\r\n    public class FirstTypeWithAttribute\r\n    {\r\n        \r\n    }\r\n    \r\n    [SomeCustomAttribute]\r\n    public class SecondTypeWithAttribute\r\n    {\r\n        \r\n    }\r\n\r\n    [AttributeUsage(AttributeTargets.Class)  \r\n    ]  \r\n    public class SomeCustomAttribute : Attribute\r\n    {\r\n        \r\n    }\r\n}\r\n```\r\n\r\nOf course, above implementation is quite naive, e.g. it's not thread-safe. We could enhance and simplify that by using [ConcurrentDictionary](https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/how-to-add-and-remove-items) class:\r\n\r\n```csharp\r\npublic static Func\u003cTInput, TResult\u003e Memoize\u003cTInput, TResult\u003e(this Func\u003cTInput, TResult\u003e func)\r\n{\r\n    // create cache (\"memo\")\r\n    var memo = new ConcurrentDictionary\u003cTInput, TResult\u003e();\r\n\r\n    // wrap provided function with cache handling\r\n    // get a value from cache if it exists\r\n    // if not, call factory method\r\n    // ConcurrentDictionary will handle that internally\r\n    return input =\u003e memo.GetOrAdd(input, func);\r\n}\r\n```\r\n\r\nThis version is still not perfect. When we are memoizing many items, our cache may grow exponentially and generate a memory usage issue. Generally, we'd like to keep in memory only the actively accessed entries. Not accessed, we can evict. We'd like to be able to set up a top limit of entries in our cache. To do that, we can use a [MemoryCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-5.0) class. A sample implementation can look like this:\r\n\r\n```csharp\r\npublic static Func\u003cTInput, TResult\u003e Memoize\u003cTInput, TResult\u003e(this Func\u003cTInput, TResult\u003e func)\r\n{\r\n    // create cache (\"memo\")\r\n    var memo = new MemoryCache(new MemoryCacheOptions\r\n    {\r\n        // Set cache size limit.\r\n        // Note: this is not size in bytes,\r\n        // but sum of all entries' sizes.\r\n        // Entry size is declared on adding to cache\r\n        // in the factory method \r\n        SizeLimit = 100\r\n    });\r\n    \r\n    // wrap provided function with cache handling\r\n    // get a value from cache if it exists\r\n    // if not, call factory method\r\n    // MemCache will handle that internally\r\n    return input =\u003e memo.GetOrCreate(input, entry =\u003e\r\n    {\r\n        // you can set different options like e.g.\r\n        // sliding expiration - time between now and last time\r\n        // and the last time the entry was accessed \r\n        entry.SlidingExpiration = TimeSpan.FromSeconds(3);\r\n        \r\n        // this value is used to calculate total SizeLimit\r\n        entry.Size = 1;\r\n        return func(input);\r\n    });\r\n}\r\n```\r\n\r\nIn functional programming, recursion is a widespread practice. It's non-trivial as to understand recursion, you need to understand recursion. It can be computation expensive. How to use the Memoization with recursion? Let's take the Fibonacci sequence as an example. The rule is: the next number is found by adding up the two numbers before it.\r\n\r\n```csharp\r\nint Fibonacci(int n1)\r\n{\r\n    if (n1 \u003c= 2)\r\n        return 1;\r\n                \r\n    return Fibonacci(n1 -1) + Fibonacci(n1 - 2);\r\n}\r\n```\r\n\r\nWe'll need to find a way to inject the memoized version of the Fibonacci function. Let's start with breaking out function into the main and the overload:\r\n\r\n```csharp\r\nint Fibonacci(int n1)\r\n{\r\n    return Fibonacci(n1, Fibonacci);\r\n}\r\n\r\nint Fibonacci(int n1, Func\u003cint, int\u003e fibonacci)\r\n{        \r\n    if (n1 \u003c= 2)\r\n        return 1;\r\n        \r\n    return fibonacci(n1 -1) + fibonacci(n1 - 2);\r\n}\r\n```\r\n\r\nNow instead of the direct self-call, we can inject the function to use while doing recursion. Now we have the possibility to memoize it by doing:\r\n\r\n```csharp\r\nFunc\u003cint, int\u003e fibonacci = null;\r\n            \r\nfibonacci = Memoizer.Memoize((int n1)  =\u003e Fibonacci(n1, fibonacci));\r\n            \r\nvar result = fibonacci(3);\r\n```\r\n\r\nThe other way is to use the local function:\r\n\r\n```csharp\r\nFunc\u003cint, int\u003e fibonacci = null;\r\n\r\nfibonacci = n1 =\u003e\r\n{\r\n    numberOfCalls++;\r\n    \r\n    if (n1 \u003c= 2)\r\n        return 1;\r\n    \r\n    return fibonacci(n1 - 1) + fibonacci(n1 - 2);\r\n};\r\n\r\nfibonacci = fibonacci.Memoize();\r\n\r\n\r\nvar result = fibonacci(3);\r\n```\r\n\r\nThe trick is that the local _fibonacci_ function is lazily evaluated. That means that effectively it will use the assigned, memoized function while doing the call. I know that analyzing recursion can create a headache. It may be more accessible by debugging the tests:\r\n- [Regular function](./Memoization.Tests/RecurrsionWithFunctionTests.cs)\r\n- [Local function](./Memoization.Tests/RecurrsionWithLocalFunctionTests.cs)\r\n\r\nSee also:\r\n- Simple implementation: [Memoizer.cs](./Memoization/Memoizer.cs)\r\n- Thread-Safe implementation with [ConcurrentDictionary](https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/how-to-add-and-remove-items): [ThreadSafeMemoizer.cs](./Memoization/ThreadSafeMemoizer.cs)\r\n- Implementation with cache eviction with a [MemoryCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-5.0): [MemoryCacheMemoizer.cs](./Memoization/MemoryCacheMemoizer.cs),\r\n- the [Program.cs](./Memoization/Program.cs) to debug the code.\r\n\r\nRead more in my article [\"Memoization, a useful pattern for quick optimization\"](https://event-driven.io/en/memoization_a_useful_pattern_for_quick_optimisation).\r\n\r\n## TO DO\r\n- [x] - basic sample and introduction\r\n- [x] - thread-safe sample using `ConcurrentDictionary`\r\n- [ ] - enhance the type check for reference type params\r\n- [ ] - Use generators or currying techniques\r\n- [ ] - Sample in JavaScript/TypeScript\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foskardudycz%2Fmemoization","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foskardudycz%2Fmemoization","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foskardudycz%2Fmemoization/lists"}