重构并删除此记忆代码中的重复内容

发布于 2024-10-11 04:44:59 字数 2852 浏览 7 评论 0原文

我试图从这段代码中删除一些重复项,并让它轻松支持具有更多参数的函数。

您将如何改进这段代码并允许更复杂的功能?

另外,我担心我的密钥生成,某些对象不会明确序列化为字符串,而只是返回它们的类型名,而不是唯一值。建议?

编辑:我使用了 ChaosPandion 的答案,并将其归结为

using System;
using System.Web.Caching;

public static class Memoize
{
    public static Cache LocalCache = System.Web.HttpRuntime.Cache ?? System.Web.HttpContext.Current.Cache;

    public static TResult ResultOf<TArg1, TResult>(Func<TArg1, TResult> func, long durationInSeconds, TArg1 arg1)
    {
        var key = HashArguments(func.Method.Name, arg1);
        return ResultOf(key, durationInSeconds, () => func(arg1));
    }

    public static TResult ResultOf<TArg1, TArg2, TResult>(Func<TArg1, TArg2, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2)
    {
        var key = HashArguments(func.Method.Name, arg1, arg2);
        return ResultOf(key, durationInSeconds, () => func(arg1, arg2));
    }

    public static TResult ResultOf<TArg1, TArg2, TArg3, TResult>(Func<TArg1, TArg2, TArg3, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2, TArg3 arg3)
    {
        var key = HashArguments(func.Method.Name, arg1, arg2, arg3);
        return ResultOf(key, durationInSeconds, () => func(arg1, arg2, arg3));
    }

    public static TResult ResultOf<TArg1, TArg2, TArg3, TArg4, TResult>(Func<TArg1, TArg2, TArg3, TArg4, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
    {
        var key = HashArguments(func.Method.Name, arg1, arg2, arg3, arg4);
        return ResultOf(key, durationInSeconds, () => func(arg1, arg2, arg3, arg4));
    }

    private static TResult ResultOf<TResult>(string key, long durationInSeconds, Func<TResult> func)
    {
        return LocalCache.Get(key) != null
                   ? (TResult)LocalCache.Get(key)
                   : CacheResult(key, durationInSeconds, func());
    }

    public static void Reset()
    {
        var enumerator = LocalCache.GetEnumerator();
        while (enumerator.MoveNext())
            LocalCache.Remove(enumerator.Key.ToString());
    }

    private static T CacheResult<T>(string key, long durationInSeconds, T value)
    {
        LocalCache.Insert(key, value, null, DateTime.Now.AddSeconds(durationInSeconds), new TimeSpan());
        return value;
    }

    private static string HashArguments(params object[] args)
    {
        if (args == null)
            return "noargs";

        var result = 23;
        for (var i = 0; i < args.Length; i++)
        {
            var arg = args[i];
            if (arg == null)
            {
                result *= (31 * i + 1);
                continue;
            }
            result *= (31 * arg.GetHashCode());
        }
        return result.ToString();
    }
}

I'm trying to remove some duplication from this code, and have it easily support functions with more parameters.

How would you improve this code and allow for more complex functions?

Also, I'm worried about my key generation, some objects won't serialize to a string distinctly, and just return their typename, not a unique value. Suggestions?

Edit: I've used ChaosPandion's answer, and got it down to this

using System;
using System.Web.Caching;

public static class Memoize
{
    public static Cache LocalCache = System.Web.HttpRuntime.Cache ?? System.Web.HttpContext.Current.Cache;

    public static TResult ResultOf<TArg1, TResult>(Func<TArg1, TResult> func, long durationInSeconds, TArg1 arg1)
    {
        var key = HashArguments(func.Method.Name, arg1);
        return ResultOf(key, durationInSeconds, () => func(arg1));
    }

    public static TResult ResultOf<TArg1, TArg2, TResult>(Func<TArg1, TArg2, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2)
    {
        var key = HashArguments(func.Method.Name, arg1, arg2);
        return ResultOf(key, durationInSeconds, () => func(arg1, arg2));
    }

    public static TResult ResultOf<TArg1, TArg2, TArg3, TResult>(Func<TArg1, TArg2, TArg3, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2, TArg3 arg3)
    {
        var key = HashArguments(func.Method.Name, arg1, arg2, arg3);
        return ResultOf(key, durationInSeconds, () => func(arg1, arg2, arg3));
    }

    public static TResult ResultOf<TArg1, TArg2, TArg3, TArg4, TResult>(Func<TArg1, TArg2, TArg3, TArg4, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
    {
        var key = HashArguments(func.Method.Name, arg1, arg2, arg3, arg4);
        return ResultOf(key, durationInSeconds, () => func(arg1, arg2, arg3, arg4));
    }

    private static TResult ResultOf<TResult>(string key, long durationInSeconds, Func<TResult> func)
    {
        return LocalCache.Get(key) != null
                   ? (TResult)LocalCache.Get(key)
                   : CacheResult(key, durationInSeconds, func());
    }

    public static void Reset()
    {
        var enumerator = LocalCache.GetEnumerator();
        while (enumerator.MoveNext())
            LocalCache.Remove(enumerator.Key.ToString());
    }

    private static T CacheResult<T>(string key, long durationInSeconds, T value)
    {
        LocalCache.Insert(key, value, null, DateTime.Now.AddSeconds(durationInSeconds), new TimeSpan());
        return value;
    }

    private static string HashArguments(params object[] args)
    {
        if (args == null)
            return "noargs";

        var result = 23;
        for (var i = 0; i < args.Length; i++)
        {
            var arg = args[i];
            if (arg == null)
            {
                result *= (31 * i + 1);
                continue;
            }
            result *= (31 * arg.GetHashCode());
        }
        return result.ToString();
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

与往事干杯 2024-10-18 04:44:59

这是我的版本,它消除了相当多的重复,并且应该产生更可靠的密钥范围。

编辑

我的最新版本产生了一个非常可靠的发行版。看一下我放置在 Main 方法中的示例。

class Program
{

    public static class Memoize
    {
        public static Cache LocalCache = 
            System.Web.HttpRuntime.Cache ?? System.Web.HttpContext.Current.Cache;

        public static TResult ResultOf<TArg1, TResult>(
            Func<TArg1, TResult> func, 
            TArg1 arg1, 
            long durationInSeconds)
        {
            var key = HashArguments(
                func.Method.DeclaringType.GUID,
                typeof(TArg1).GUID, 
                (object)arg1);
            return Complete(key, durationInSeconds, () => func(arg1));
        }

        public static TResult ResultOf<TArg1, TArg2, TResult>(
            Func<TArg1, TArg2, TResult> func, 
            TArg1 arg1, 
            TArg2 arg2, 
            long durationInSeconds)
        {
            var key = HashArguments(
                func.Method.DeclaringType.GUID,
                typeof(TArg1).GUID, 
                (object)arg1, 
                typeof(TArg2).GUID, 
                (object)arg2);
            return Complete(key, durationInSeconds, () => func(arg1, arg2));
        }

        public static TResult ResultOf<TArg1, TArg2, TArg3, TResult>(
            Func<TArg1, TArg2, TArg3, TResult> func, 
            TArg1 arg1, 
            TArg2 arg2, 
            TArg3 arg3, 
            long durationInSeconds)
        {
            var key = HashArguments(
                func.Method.DeclaringType.GUID,
                typeof(TArg1).GUID, 
                (object)arg1, 
                typeof(TArg2).GUID, 
                (object)arg2, 
                typeof(TArg3).GUID, 
                (object)arg3);
            return Complete(key, durationInSeconds, () => func(arg1, arg2, arg3));
        }

        public static void Reset()
        {
            var enumerator = LocalCache.GetEnumerator();
            while (enumerator.MoveNext())
                LocalCache.Remove(enumerator.Key.ToString());
        }

        private static T CacheResult<T>(string key, long durationInSeconds, T value)
        {
            LocalCache.Insert(
                key, 
                value, 
                null, 
                DateTime.Now.AddSeconds(durationInSeconds), 
                new TimeSpan());
            return value;
        }

        static T Complete<T>(string key, long durationInSeconds, Func<T> valueFunc)
        {
            return LocalCache.Get(key) != null
               ? (T)LocalCache.Get(key)
               : CacheResult(key, durationInSeconds, valueFunc());
        }

        static string HashArguments(params object[] args)
        {
            if (args == null)
                return "null args";

            int result = 23;
            for (int i = 0; i < args.Length; i++)
            {
                var arg = args[i];
                if (arg == null)
                {
                    result = 31 * result + (i + 1);
                    continue;
                }
                result = 31 * result + arg.GetHashCode();
            }
            return result.ToString();
        }
    }

    static int test(int a, int b)
    {
        return a + b;
    }

    private static class Inner
    {
        public static int test(int a, int b)
        {
            return a + b;
        }
    }

    static int test(int a, object b)
    {
        return a + (int)b;
    }

    static void Main(string[] args)
    {
        Memoize.ResultOf<int, int, int>(test, 1, 2, 100000);
        Memoize.ResultOf<int, int, int>(test, 2, 1, 100000);
        Memoize.ResultOf<int, int, int>(Inner.test, 1, 2, 100000);
        Memoize.ResultOf<int, int, int>(Inner.test, 2, 1, 100000);
        Memoize.ResultOf<int, object, int>(test, 1, 2, 100000);
        Memoize.ResultOf<int, object, int>(test, 2, 1, 100000);
    }
}

Here is my version which removes quite a bit of duplication and should produce a much more reliable range of keys.

EDIT

My latest version produces a pretty reliable distribution. Take a look at the example I placed in the Main method.

class Program
{

    public static class Memoize
    {
        public static Cache LocalCache = 
            System.Web.HttpRuntime.Cache ?? System.Web.HttpContext.Current.Cache;

        public static TResult ResultOf<TArg1, TResult>(
            Func<TArg1, TResult> func, 
            TArg1 arg1, 
            long durationInSeconds)
        {
            var key = HashArguments(
                func.Method.DeclaringType.GUID,
                typeof(TArg1).GUID, 
                (object)arg1);
            return Complete(key, durationInSeconds, () => func(arg1));
        }

        public static TResult ResultOf<TArg1, TArg2, TResult>(
            Func<TArg1, TArg2, TResult> func, 
            TArg1 arg1, 
            TArg2 arg2, 
            long durationInSeconds)
        {
            var key = HashArguments(
                func.Method.DeclaringType.GUID,
                typeof(TArg1).GUID, 
                (object)arg1, 
                typeof(TArg2).GUID, 
                (object)arg2);
            return Complete(key, durationInSeconds, () => func(arg1, arg2));
        }

        public static TResult ResultOf<TArg1, TArg2, TArg3, TResult>(
            Func<TArg1, TArg2, TArg3, TResult> func, 
            TArg1 arg1, 
            TArg2 arg2, 
            TArg3 arg3, 
            long durationInSeconds)
        {
            var key = HashArguments(
                func.Method.DeclaringType.GUID,
                typeof(TArg1).GUID, 
                (object)arg1, 
                typeof(TArg2).GUID, 
                (object)arg2, 
                typeof(TArg3).GUID, 
                (object)arg3);
            return Complete(key, durationInSeconds, () => func(arg1, arg2, arg3));
        }

        public static void Reset()
        {
            var enumerator = LocalCache.GetEnumerator();
            while (enumerator.MoveNext())
                LocalCache.Remove(enumerator.Key.ToString());
        }

        private static T CacheResult<T>(string key, long durationInSeconds, T value)
        {
            LocalCache.Insert(
                key, 
                value, 
                null, 
                DateTime.Now.AddSeconds(durationInSeconds), 
                new TimeSpan());
            return value;
        }

        static T Complete<T>(string key, long durationInSeconds, Func<T> valueFunc)
        {
            return LocalCache.Get(key) != null
               ? (T)LocalCache.Get(key)
               : CacheResult(key, durationInSeconds, valueFunc());
        }

        static string HashArguments(params object[] args)
        {
            if (args == null)
                return "null args";

            int result = 23;
            for (int i = 0; i < args.Length; i++)
            {
                var arg = args[i];
                if (arg == null)
                {
                    result = 31 * result + (i + 1);
                    continue;
                }
                result = 31 * result + arg.GetHashCode();
            }
            return result.ToString();
        }
    }

    static int test(int a, int b)
    {
        return a + b;
    }

    private static class Inner
    {
        public static int test(int a, int b)
        {
            return a + b;
        }
    }

    static int test(int a, object b)
    {
        return a + (int)b;
    }

    static void Main(string[] args)
    {
        Memoize.ResultOf<int, int, int>(test, 1, 2, 100000);
        Memoize.ResultOf<int, int, int>(test, 2, 1, 100000);
        Memoize.ResultOf<int, int, int>(Inner.test, 1, 2, 100000);
        Memoize.ResultOf<int, int, int>(Inner.test, 2, 1, 100000);
        Memoize.ResultOf<int, object, int>(test, 1, 2, 100000);
        Memoize.ResultOf<int, object, int>(test, 2, 1, 100000);
    }
}
醉城メ夜风 2024-10-18 04:44:59

ChaosPandion 代码的基本思想是正确的,但是为了使其可靠地工作,必须修复一些致命的缺陷。

1)您需要创建一个真正唯一的密钥,而不是使用哈希值的哈希值。一种方法是使用分隔符连接参数数组中每个元素的字符串表示形式。这样,一旦结果被记忆,就会一致地检索它,而不会出现误报。

Chaos 的方法 GUID 技巧在这里很有用,但这避免了依赖 GetHashCode 真正唯一以及依靠相当简单的哈希值来保持唯一的问题。相反,完整的字符串将被散列,但将进行完整的逐字符比较以排除不匹配。

诚然,连接一个大字符串并不是特别优雅,甚至可能会影响性能。此外,这仍然给您留下了 ToString 不提供特定于实例的信息的类的问题。然而,有一些基于反射的解决方案。

2) 一个非常简单的优化是在 Complete 中仅调用一次 LocalCache.Get。这是一项潜在昂贵的操作,因此没有理由将成本加倍。

除此之外,请遵循 ChaosPandion 的建议。

The basic idea in ChaosPandion's code is correct, but there are some fatal flaws that must be fixed in order for this to work reliably.

1) Rather than using a hash of hashes, you need to make a genuinely unique key. One way would be to concatenate the string representations of each element in the parameter array, with a delimiter. This way, once a result has been memoized, it will consistently be retrieved, without false positives.

Chaos' trick with the GUID of the method is useful here, but this avoids the problem of depending on GetHashCode being truly unique as well as counting on a fairly simple hash of hashes to remain unique. Instead, the full string will get hashed, but there will be a full character-by-character comparison to rule out mismatches.

Admittedly, concatenating a big string is not particularly elegant and might even impact performance. Moreover, this still leaves you with the issue of classes whose ToString does not give instance-specific information. However, there are solutions for this based on reflection.

2) A very simple optimization would be to call LocalCache.Get just once in Complete. This is a potentially expensive operation, so there's really no excuse for doubling the cost.

Other than that, go with what ChaosPandion suggested.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文