在C#

时间:2018-11-13 16:23:10

标签: c# functional-programming memoization

我知道这个话题(记忆)已经讨论了很多(例如here),但是没有答案我可以满足我对DRY原理的满意程度,所以请阅读整个问题并我想讲的三点。

我有一个像这样的简单支持类:

public class Memoized<T1, TResult>
{
    private readonly Func<T1, TResult> _f;

    private readonly Dictionary<T1, TResult> _cache = new Dictionary<T1, TResult>();

    public Memoized(Func<T1, TResult> f)
    {
        _f = f;
    }

    public TResult Invoke(T1 p1)
    {
        if (p1 == null) throw new ArgumentNullException(nameof(p1));

        if (_cache.TryGetValue(p1, out var res)) return res;

        return _cache[p1] = _f(p1);
    }
    public static Func<T1, TResult> Of(Func<T1, TResult> f)
    {
        var memo = new Memoized<T1, TResult>(f);

        return x => memo.Invoke(x);
    }
}

没什么特别花哨的,但是它允许我这样做:

public class MyClass
{
    public Func<int, bool> MemoizedMethod { get; }

    private bool UncachedMethod(int v)
    {
        return v > 0;
    }

    public MyClass()
    {
        MemoizedMethod = Memoized<int, bool>.Of(UncachedMethod);
    }
}

现在,即使生成的代码不是非常嘈杂,我仍在尝试确定实现是否可以更优雅,因为目前我需要

  1. 作为方法的可调用属性。
  2. 不应该直接调用的真实方法。
  3. 构造函数中的一行(不能是内联初始化),将两者链接在一起(函数签名的第三次重复!)。

任何建议删除上述一个或两个要点的策略都是很好的。

2 个答案:

答案 0 :(得分:1)

如果您在lambda中捕获字典,则状态将被隐式维护。

public class Memoized
{
    // Example with a single parameter. That parameter becomes the key to the dictionary.
    public static Func<T1, TRet> Of<T1, TRet>(Func<T1, TRet> f)
    {
        ConcurrentDictionary<T1, TRet> cache = new ConcurrentDictionary<T1, TRet>();
        return (arg1) => cache.GetOrAdd(arg1, xarg=>f(xarg));
    }
    // Example with two parameters. The key is a tuple, and it must be unpacked before calling func.
    // Three or more parameters generalize from this
    public static Func<T1, T2, TRet> Of<T1, T2, TRet>(Func<T1, T2, TRet> f)
    {
        ConcurrentDictionary<Tuple<T1,T2>, TRet> cache = new ConcurrentDictionary<Tuple<T1, T2>, TRet>();
        return (arg1, arg2) => cache.GetOrAdd(new Tuple<T1,T2>(arg1, arg2), 
            (xarg)=>f(xarg.Item1, xarg.Item2)
            );
    }

}

用法示例:

class Test
{
    public int Method(String s, String s2)
    {
        return 99;
    }
}
class Program
{
    static bool UncachedMethod(int x) { return x > 0; }
    static void Main(string[] args)
    {
        var cached = Memoized.Of<int,bool>(UncachedMethod);

        var exampleCall = cached(44);
        var exampleCall2 = cached(44);

        // Capture a non-static member function
        var x = new Test();
        var cachedX = Memoized.Of<String, String,int>(x.Method);

        var exampleCall3 = cachedX("a","b");

    }
}

答案 1 :(得分:0)

在追求优雅的过程中,我终于找到了我认为在任何地方都能看到的最佳语法:

PATH

实现(一个很小的扩展类):

private class MemoizedTest
{
    private int _counter = 0;

    public int Method(int p) => this.Memoized(p, x =>
    {
        return _counter += x;
    });
}

说明 在实现中,我使用namespace System { public static class MemoizerExtension { internal static ConditionalWeakTable<object, ConcurrentDictionary<string, object>> _weakCache = new ConditionalWeakTable<object, ConcurrentDictionary<string, object>>(); public static TResult Memoized<T1, TResult>( this object context, T1 arg, Func<T1, TResult> f, [CallerMemberName] string cacheKey = null) { if (context == null) throw new ArgumentNullException(nameof(context)); if (cacheKey == null) throw new ArgumentNullException(nameof(cacheKey)); var objCache = _weakCache.GetOrCreateValue(context); var methodCache = (ConcurrentDictionary<T1, TResult>) objCache .GetOrAdd(cacheKey, _ => new ConcurrentDictionary<T1, TResult>()); return methodCache.GetOrAdd(arg, f); } } } 进行缓存,有效地扩展了调用备忘录的对象的内部结构。作为附加键,使用ConditionalWeakTable作为第二个键(例如,如果显式传递了CallerMemberName参数,则这允许更多的备注,并且每个方法还可以选择更多的备注)。 第三个键是调用的参数。

因此,我们有3种类似于运行时字典的搜索,而不是1,但是语法更加简洁,IMO。

值得吗?我不知道,但我对优雅的渴望得到满足。

如果其他人有兴趣,我将其中的测试内容作为参考:

cacheKey