我可以记住通用方法吗?

问题描述 投票:0回答:1

我有 2 个昂贵的通用方法:

  public T DoStuff<T>()
  {
      //return depends on T.
  }

  public T DoStuffBasedOnString<T>(string input)
  {
      //return depends on T.
  }

对于给定的类型和字符串,它们的返回值永远不会变化。

这些功能可以记忆吗?


我的出发点是从这里获取的 memoize 函数: https://www.aleksandar.io/post/memoization/

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);
}
c# generics memoization
1个回答
0
投票

我在这里用Linqpad 7写了一个demo,看起来可行。

void Main()
{
    var doStuff = () => DoStuff<Car>();
    var doStuffMemoized = doStuff.Memoize();
    var stopWatch = Stopwatch.StartNew();
    var car = doStuffMemoized();
    Console.WriteLine($"Retrieving car took:  {stopWatch.ElapsedMilliseconds} ms");
    stopWatch.Reset();
    car = doStuffMemoized(); //second call uses cached result and return almost instantly
    Console.WriteLine($"Retrieving car took:  {stopWatch.ElapsedMilliseconds} ms");

    var doStuffBasedOnString = (string arg) => DoStuffBasedOnString<Car>(arg);
    var doStuffBasedOnStringM = doStuffBasedOnString.Memoize(x => x);
    stopWatch = Stopwatch.StartNew();
    var anotherCar = doStuffBasedOnStringM("Volvo 240");
    Console.WriteLine($"Retrieving car took:  {stopWatch.ElapsedMilliseconds} ms");
    stopWatch.Reset();  
    anotherCar = doStuffBasedOnStringM("Volvo 240"); //second call uses cached result and return almost instantly
    Console.WriteLine($"Retrieving car took:  {stopWatch.ElapsedMilliseconds} ms");

}

public class Car {
    public string Model { get; set; }
    public string Make { get; set; }
}

public T DoStuff<T>() where T : class, new()
{
    Thread.Sleep(1000);
    var result = Activator.CreateInstance<T>();
    return result;
}

public T DoStuffBasedOnString<T>(string input)
{
    Thread.Sleep(2000);
    var result = Activator.CreateInstance<T>();
    return result;
}


public static class FunctionalExtensions
{

    public static Func<TOut> Memoize<TOut>(this Func<TOut> @this)
    {
        var dict = new ConcurrentDictionary<string, TOut>();
        return () =>
        {
            string key = typeof(TOut).FullName;
            if (!dict.ContainsKey(key))
            {
                dict.TryAdd(key, @this());
            }
            return dict[key];
        };
    }



    public static Func<T1, TOut> Memoize<T1, TOut>(this Func<T1, TOut> @this, Func<T1, string> keyGenerator)
    {
        var dict = new ConcurrentDictionary<string, TOut>();
        return x =>
        {
            string key = keyGenerator(x);
            if (!dict.ContainsKey(key))
            {
                dict.TryAdd(key, @this(x));
            }
            return dict[key];
        };
    }

}

我之前写过 Memoize 方法,但零参数在 Memoize 中并不常见。我们只是将完整类型名称缓存到 ConcurrentDictionary 中。对于接受字符串参数的方法中的“密钥生成器”,我只使用 'x => x' ,这是一种方法,您可以在该方法中告诉您如何指定进入 ConcurrentDictionary 的密钥,该密钥控制我们是否应该缓存。

Linqpad 屏幕截图显示其工作原理。 (我也调试了一下)

Memoize demo - run in Linqpad 7

现在,请注意,一旦您拥有接受一个参数的 memoize 方法,您可能希望拥有也接受多个参数的重载,它们应该相当容易扩展。

我在这里为感兴趣的人写了一篇文章,它最多有四个论点。有一些内置库已经实现了 Memoize。例如,GitHub 库上的 LanguageExt for C#。我见过的大多数示例都只使用字典,只有在您确实怀疑需要它来保证线程安全时才使用 ConcurrentDictionary,有时您的应用程序仅以不必要的方式运行。

© www.soinside.com 2019 - 2024. All rights reserved.