How to ensure the logic of a method is executed only once for a combination of arguments?

I am developing a class library that has a bunch of method like "EnsureXXX". The idea of ​​these methods should be called up whenever the calling code requires something that may require initialization specific to the arguments. This is similar to the ASP.Net EnsureChildControls method, but with arguments as discriminators.

Example:

 public static class SomeUtilityClass { public static void EnsureSomething(string arg1, int arg2, object arg3) { // Logic should be called once for each args combination } } public class CallerClass { public void Foo() { SomeUtilityClass.EnsureSomething("mycustomerid", 4, myData.SomeProperty); } public void Foo2() { SomeUtilityClass.EnsureSomething("mycustomerid", 4, myData.SomeProperty); } } 

Since such a template will be reused in several places and is called very often, I have to maintain performance as a goal. I must also have a thread safe method.

To this end, I wrote a small utility class:

 public sealed class CallHelper { private static readonly HashSet<int> g_YetCalled = new HashSet<int>(); private static readonly object g_SyncRoot = new object(); public static void EnsureOnce(Type type, Action a, params object[] arguments) { // algorithm for hashing adapted from http://stackoverflow.com/a/263416/588868 int hash = 17; hash = hash * 41 + type.GetHashCode(); hash = hash * 41 + a.GetHashCode(); for (int i = 0; i < arguments.Length; i++) { hash = hash * 41 + (arguments[i] ?? 0).GetHashCode(); } if (!g_YetCalled.Contains(hash)) { lock (g_SyncRoot) { if (!g_YetCalled.Contains(hash)) { a(); g_YetCalled.Add(hash); } } } } } 

The consumption code is as follows:

 public static class Program { static void Main() { SomeMethod("1", 1, 1); SomeMethod("2", 1, 1); SomeMethod("1", 1, 1); SomeMethod("1", 1, null); Console.ReadLine(); } static void SomeMethod(string arg1, int arg2, object arg3) { CallHelper.EnsureOnce(typeof(Program), ()=> { Console.WriteLine("SomeMethod called only once for {0}, {1} and {2}", arg1, arg2, arg3); }, arg1, arg2, arg3); } } 

The output, as expected:

 SomeMethod called only once for 1, 1 and 1 SomeMethod called only once for 2, 1 and 1 SomeMethod called only once for 1, 1 and 

I have some questions related to this approach:

  • I think I correctly locked the class to ensure thread safety, but am I right?
  • Is a HashSet<int> and my hash calculation method correct? I am particularly interested in the proper handling of null , and if I can delegate an Action delegate this way.
  • My methods currently only support static methods. How can I go to an instance of a compatible method (adding an instance as a discriminator) without memory leak?
  • Is there a way to avoid passing all arguments manually to a utility method (just specifying an action) without exploring stacktrace (due to performance impact)? I am afraid that many errors have appeared due to missing arguments from an external method.

Thanks in advance

+6
source share
1 answer

This is essentially memoize , except that your function is not valid. But the same considerations for comparing the input arguments remain valid.

Wes Dyer discusses how to make a general comment with a few arguments in this post . The general idea is to collapse all arguments into an anonymous type and use this as a dictionary key.

Regarding creating this thread-safe, consider that .NET already has parallel collections . You do not need to create a non-competitive collection at the same time; just use the provided collections.

To make this work, for example, without leakage methods, either save the WeakReference in the instance, or save the memoizer instance in the instance itself, depending on what works for you.

+5
source

Source: https://habr.com/ru/post/905822/


All Articles