• Code
  • Tags
  • Users
  • Titles
  • Log in
  • Feedback
  • FAQ
Share Code
Welcome to ForkCan.com

ForkCan is all about sharing code in a social way.

Discuss, debate or argue with other devs about their or your own code.

Give other devs feedback or make a Fork (Make a better version of a shared code).

Rate the code, if you use the code mark it as used so others can see if the shared code is used by someone.

Help each other to be better devs and to be more productive.


Features not working yet:

Flag a post


QR Code

Tiny Url

http://4kcan.com/s/MzA5

Related Code
Using DynamicObject to get a value from the Request.Params
Fork of GZIP your Actions in MVC if not used as standard in IIS7 or only use IIS6
ActionFilter to Handle and Log Error for each Controller, ASP.NET MVC
Fork of Serialize an object into a Querystring, using LINQ syntax
Serialize an object into a Querystring
Get Cookies in Web Api Asp .net Mvc 4 RC
GZIP your Actions in MVC if not used as standard in IIS7 or only use IIS6

A simple way to cache slow functions in C#

A set of methods which helps you extend your functions so that they cache their result after execution.

1
1.2k 0 0 0 0 1

LambdaCache helps you to make expensive function calls cacheable. Instead of calling slowFunction(inputVar); you run LambdaCache.ToCachedFunction(() => slowFunction(inputVar), TimeSpan.FromHours(24), inputVar)(); to make the function cache it's result for 24 hours.

#region

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;

#endregion

namespace Utilities
{
    /// <summary>
    /// LambdaCache helps you to make expensive function calls cacheable. 
    /// For instance, instead of calling <code>expensiveFunction(inputVar);</code>
    /// you run <code>LambdaCache.ToCachedFunction(() => expensiveFunction(inputVar), TimeSpan.FromHours(24), inputVar)();</code>
    /// to make the function cache it's result for 24 hours.
    /// 
    /// The methods lock on the input (input-vars + function-name) to make sure that concurrent calls to the same function with
    /// the same input are not called twice. 
    /// </summary>
    public static class LambdaCache
    {
        private static bool cacheIsEnabled
        {
            get { return HttpContext.Current != null; }
        }

        /// <summary>
        /// Makes your function cache its return value in HttpContext.Cache. 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expensiveFunction">The function to cache.</param>
        /// <param name="timeToCache">Time to cache it.</param>
        /// <param name="cacheKeys">The input variables which the function's output depends on.</param>
        /// <returns></returns>
        public static Func<T> ToCachedFunction<T>(this Func<T> expensiveFunction, TimeSpan timeToCache, params object[] cacheKeys)
        {
            return ToCachedFunction(expensiveFunction, timeToCache, CacheLocation.Cache, cacheKeys);
        }

        public static Func<T> ToCachedFunction<T>(this Func<T> expensiveFunction, DateTime cacheDate, params object[] cacheKeys)
        {
            return ToCachedFunction(expensiveFunction, cacheDate, CacheLocation.Cache, cacheKeys);
        }

        /// <summary>
        /// Works the same way as ToCachedFunction, except that it stores the result in HttpContext.Items (i.e. within the current http-request).
        /// Therefore you don't have to say how long time to cache it.
        /// </summary>
        /// <param name="expensiveFunction">The function to cache.</param>
        /// <param name="cacheKeys">The input variables which the function's output depends on.</param>
        /// <returns></returns>
        public static Func<T> ToRequestCachedFunction<T>(this Func<T> expensiveFunction, params object[] cacheKeys)
        {
            return ToCachedFunction(expensiveFunction, TimeSpan.Zero, CacheLocation.Request, cacheKeys);
        }

        /// <summary>
        /// Works the same way as ToCachedFunction, except that it stores the result in HttpContext.Session (i.e. within the current http-session).
        /// Therefore you don't have to say how long time to cache it.
        /// </summary>
        /// <param name="expensiveFunction">The function to cache.</param>
        /// <param name="cacheKeys">The input variables which the function's output depends on.</param>
        /// <returns></returns>
        public static Func<T> ToSessionCachedFunction<T>(this Func<T> expensiveFunction, params object[] cacheKeys)
        {
            return ToCachedFunction(expensiveFunction, TimeSpan.Zero, CacheLocation.Session, cacheKeys);
        }


        private static Func<T> ToCachedFunction<T>(this Func<T> expensiveFunction, DateTime endTime, CacheLocation cacheLocation,
                                                   params object[] cacheKeys)
        {
            var timeToCache = endTime.Subtract(DateTime.Now);
            return ToCachedFunction(expensiveFunction, timeToCache, cacheLocation, cacheKeys);
        }

        private static string getCacheKey<T>(Func<T> expensiveFunction, IEnumerable<object> localCacheKey)
        {
            var cacheKeyBuilder = new StringBuilder(expensiveFunction.ToString());
            var textWriter = new StringWriter(cacheKeyBuilder);
            var serializer = new LosFormatter(false, "");

            foreach (var cacheKey in localCacheKey)
            {
                cacheKeyBuilder.Append('|');
                if (cacheKey != null)
                {
                    //Diffrerent instances of DateTime which represents the same value
                    //sometimes serialize differently due to some internal variables which are different.
                    //We therefore serialize it using Ticks instead. instead.
                    var inputDateTime = cacheKey as DateTime?;
                    if (inputDateTime.HasValue)
                    {
                        cacheKeyBuilder.Append(inputDateTime.Value.Ticks);
                    }
                    else if (cacheKey is string)
                    {
                        cacheKeyBuilder.Append(cacheKey);
                    }
                    else
                    {
                        //Serialize the input and write it to the key StringBuilder.
                        serializer.Serialize(textWriter, cacheKey);
                    }
                }
                else
                {
                    cacheKeyBuilder.Append("NullValue");
                }
            }

            return String.Intern(cacheKeyBuilder.ToString());
        }

        public static void RemoveCacheData<T>(this Func<T> expensiveFunction, params object[] localCacheKey)
        {
            if (cacheIsEnabled)
            {
                string globalCacheKey = getCacheKey(expensiveFunction, localCacheKey);
                lock (globalCacheKey)
                {
                    HttpContext.Current.Cache.Remove(globalCacheKey);
                }
            }
        }

        private static Func<T> ToCachedFunction<T>(this Func<T> expensiveFunction, TimeSpan timeToCache, CacheLocation cacheLocation,
                                                   params object[] localCacheKey)
        {
            return () =>
                       {
                           //We lock the function to make sure that concurrent calls to it are not made.
                           // This will increase the cache-hit ratio since the chances of a function being called previously is greater when
                           // we wait for the function to finish its execution.
                           // We use intern to make sure we use the same 
                           string globalCacheKey = getCacheKey(expensiveFunction, localCacheKey);
                           lock (globalCacheKey)
                           {
                               if (cacheIsEnabled)
                               {
                                   Object tempCacheValue;

                                   switch (cacheLocation)
                                   {
                                       case CacheLocation.Cache:
                                           tempCacheValue = HttpContext.Current.Cache[globalCacheKey];
                                           break;
                                       case CacheLocation.Request:
                                           tempCacheValue = HttpContext.Current.Items[globalCacheKey];
                                           break;
                                       case CacheLocation.Session:
                                           tempCacheValue = HttpContext.Current.Session[globalCacheKey];
                                           break;
                                       default:
                                           throw new ArgumentOutOfRangeException("cacheLocation");
                                   }

                                   if (tempCacheValue is DefaultValue)
                                   {
                                       return default(T);
                                   }
                                   if (tempCacheValue != null)
                                   {
                                       return (T) tempCacheValue;
                                   }
                               }
#if DEBUG
                               Debug.WriteLine("Start:" + expensiveFunction + " key: " + globalCacheKey);
                               var stop = Stopwatch.StartNew();
#endif
                               Object methodResult = expensiveFunction();
#if DEBUG
                               stop.Stop();
                               Debug.WriteLine("End:" + expensiveFunction + " time: " + stop.ElapsedMilliseconds);
#endif


                               if (cacheIsEnabled)
                               {
                                   //The ASP.NET cache cannot store null-values. We therefore use this 
                                   // class-instance as a place-holder.
                                   var cacheValue = methodResult ?? new DefaultValue();
                                   switch (cacheLocation)
                                   {
                                       case CacheLocation.Cache:
                                           //TODO: Implement support for callbacks to reload the cache when it expires.
                                           HttpContext.Current.Cache.Insert(
                                               globalCacheKey,
                                               cacheValue,
                                               null,
                                               Cache.NoAbsoluteExpiration,
                                               timeToCache,
                                               CacheItemPriority.Normal, null);
                                           break;
                                       case CacheLocation.Request:
                                           HttpContext.Current.Items[globalCacheKey] = cacheValue;
                                           break;
                                       case CacheLocation.Session:
                                           HttpContext.Current.Session[globalCacheKey] = cacheValue;
                                           break;
                                       default:
                                           throw new ArgumentOutOfRangeException("cacheLocation");
                                   }
                               }
                               return (T) methodResult;
                           }
                       };
        }

        #region Nested type: CacheLocation

        private enum CacheLocation
        {
            Cache,
            Request,
            Session
        }

        #endregion

        #region Nested type: DefaultValue

        private class DefaultValue
        {
        }

        #endregion
    }
}

Share: twitter | facebook   Action: used | fork | flag

asp.net

Mark 'asp.net' tag as 'like'

Mark 'asp.net' tag as 'ignore'

c#

Mark 'c#' tag as 'like'

Mark 'c#' tag as 'ignore'

cache

Mark 'cache' tag as 'like'

Mark 'cache' tag as 'ignore'

functional

Mark 'functional' tag as 'like'

Mark 'functional' tag as 'ignore'


 Yrlec
101
October 08, 2010 7:50 PM
edited October 09, 2010 12:09 PM

Fork

 A simple way to cache slow functions in C# - Yrlec Friday 08, 2010 7:50 PM


0 Feedback


You must log in before you can give any feedback


1 Discussion(s)

Newest Oldest
1

Instead of using an Enum and hardcode the cache storage I should have used a plug-in/provider based solution. It will make sure you could use the same interface to cache against any kind of source, and remove dependencies.

link | flag  | Reply

 @fredrikn "I'm the master"
3.11k
Sunday 10, 2010 11:24 AM


You must log in before you can post a comment

Squeed
Made by: Fredrik Normén 2010