Guest User

PostSharp Memoize Aspect

a guest
Dec 19th, 2010
325
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 10.99 KB | None | 0 0
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Reflection;
  7. using PostSharp.Aspects;
  8.  
  9. /// <summary>
  10. /// Аспект, производящий мемоизацию метода, отмечаемого данным
  11. /// атрибутом. Методы с ref-/out-параметрами не поддерживаются.
  12. /// </summary>
  13. [Serializable, AttributeUsage(AttributeTargets.Method)]
  14. public sealed class MemoizeAttribute
  15.     : MethodInterceptionAspect, IInstanceScopedAspect
  16. {
  17.     /// <summary>
  18.     /// Указывает, должен ли метод поддерживать
  19.     /// корректную работу в многопоточной среде.
  20.     /// </summary>
  21.     public bool IsThreadSafe { get; set; }
  22.  
  23.     #region MemoCache
  24.  
  25.     [Serializable]
  26.     abstract class MemoCache
  27.     {
  28.         public abstract bool TryResolve(
  29.             object arg, out object result);
  30.         public abstract void AppendItem(
  31.             Arguments arg, int index, object result);
  32.     }
  33.  
  34.     [Serializable]
  35.     abstract class MemoCache<T, TResult> : MemoCache
  36.     {
  37.         static readonly Func<MemoCache> NestedCacheFactory;
  38.  
  39.         static MemoCache()
  40.         {
  41.             // если элементами кэша данного типа
  42.             // являются другие вложенные кэши
  43.             if (typeof(TResult).IsSubclassOf(typeof(MemoCache)))
  44.             {
  45.                 NestedCacheFactory =
  46.                     GetCacheFactory(typeof(TResult));
  47.             }
  48.         }
  49.  
  50.         protected abstract void AppendImpl(T arg, TResult result);
  51.  
  52.         public sealed override void AppendItem(
  53.             Arguments arg, int index, object result)
  54.         {
  55.             if (NestedCacheFactory == null)
  56.             {
  57.                 // тривиально добавляем в кэш
  58.                 AppendImpl((T) arg[index], (TResult) result);
  59.             }
  60.             else
  61.             {
  62.                 // создаём экземпляр вложенного кэша
  63.                 var nested = NestedCacheFactory();
  64.  
  65.                 AppendImpl( // и добавляем его в кэш
  66.                     (T) arg[index],
  67.                     (TResult) (object) nested);
  68.  
  69.                 // кэшируем следующий аргумент
  70.                 nested.AppendItem(arg, index + 1, result);
  71.             }
  72.         }
  73.     }
  74.  
  75.     /// <summary>
  76.     /// Вариант кэша на базе обычного словаря.
  77.     /// </summary>
  78.     [Serializable]
  79.     sealed class DictionaryCache<T, TResult>
  80.                         : MemoCache<T, TResult>
  81.     {
  82.         readonly Dictionary<T, TResult> cache
  83.             = new Dictionary<T, TResult>();
  84.  
  85.         public static MemoCache CreateInstance()
  86.         {
  87.             return new DictionaryCache<T, TResult>();
  88.         }
  89.  
  90.         public override bool TryResolve(object arg, out object result)
  91.         {
  92.             TResult value;
  93.             if (cache.TryGetValue((T) arg, out value))
  94.             {
  95.                 result = value;
  96.                 return true;
  97.             }
  98.             else
  99.             {
  100.                 result = null;
  101.                 return false;
  102.             }
  103.         }
  104.  
  105.         protected override void AppendImpl(T arg, TResult result)
  106.         {
  107.             cache.Add(arg, result);
  108.         }
  109.     }
  110.  
  111.     /// <summary>
  112.     /// Вариант кэша на базе конкурентного словаря.
  113.     /// </summary>
  114.     [Serializable]
  115.     sealed class ConcurrentCache<T, TResult>
  116.                         : MemoCache<T, TResult>
  117.     {
  118.         readonly ConcurrentDictionary<T, TResult> cache
  119.             = new ConcurrentDictionary<T, TResult>();
  120.  
  121.         public static MemoCache CreateInstance()
  122.         {
  123.             return new ConcurrentCache<T, TResult>();
  124.         }
  125.  
  126.         public override bool TryResolve(object arg, out object result)
  127.         {
  128.             TResult value;
  129.             if (cache.TryGetValue((T) arg, out value))
  130.             {
  131.                 result = value;
  132.                 return true;
  133.             }
  134.             else
  135.             {
  136.                 result = null;
  137.                 return false;
  138.             }
  139.         }
  140.  
  141.         protected override void AppendImpl(T arg, TResult result)
  142.         {
  143.             cache.AddOrUpdate(arg, result, (_, x) => x);
  144.         }
  145.     }
  146.  
  147.     /// <summary>
  148.     /// Возвращает фабрику создания экземпляров кэша по типу.
  149.     /// </summary>
  150.     static Func<MemoCache> GetCacheFactory(Type cacheType)
  151.     {
  152.         // ищем метод "public static MemoCache CreateInstance()"
  153.         var methodInfo = cacheType.GetMethod(
  154.             "CreateInstance", BindingFlags.Static | BindingFlags.Public);
  155.  
  156.         // и создаём из него делегат для быстрого создания экземпляров
  157.         return (Func<MemoCache>)
  158.             Delegate.CreateDelegate(typeof(Func<MemoCache>), methodInfo);
  159.     }
  160.  
  161.     /// <summary>
  162.     /// Создаёт тип кэша, соответствующий типам параметров
  163.     /// заданного метода и требованиям к многопоточной работе.
  164.     /// </summary>
  165.     Type GetRootCacheType(MethodInfo method)
  166.     {
  167.         Debug.Assert(method != null);
  168.  
  169.         var parameters = method.GetParameters();
  170.         var resultType = method.ReturnType;
  171.  
  172.         // определяем тип используемого кэша
  173.         var cacheType = IsThreadSafe
  174.             ? typeof(ConcurrentCache<,>)
  175.             : typeof(DictionaryCache<,>);
  176.  
  177.         // перебираем параметры с конца
  178.         for (int i = parameters.Length - 1; i >= 0; i--)
  179.         {
  180.             // формируем тип "Cache<T1, Cache<T2, Cache<T3, TResult>>>",
  181.             // в котором типы T1, T2, T3 соответствуют параметрам метода:
  182.             resultType = cacheType.MakeGenericType(
  183.                 parameters[i].ParameterType, resultType);
  184.         }
  185.  
  186.         return resultType;
  187.     }
  188.  
  189.     #endregion
  190.     #region Methods
  191.  
  192.     /// <summary>
  193.     /// Процедура валидации использования аспекта мемоизации.
  194.     /// </summary>
  195.     public override bool CompileTimeValidate(MethodBase method)
  196.     {
  197.         var mi = (MethodInfo) method;
  198.         if (mi.ReturnType == typeof(void))
  199.         {
  200.             throw new InvalidOperationException(
  201.                 "Аспект следует применять только " +
  202.                 "на методы, возвращающие значение.");
  203.         }
  204.  
  205.         var paremeters = mi.GetParameters();
  206.         if (paremeters.Length == 0)
  207.         {
  208.             throw new InvalidOperationException(
  209.                 "Аспект следует применять только " +
  210.                 "на методы, имеющие параметры.");
  211.         }
  212.  
  213.         foreach (var parameter in paremeters)
  214.         {
  215.             if (parameter.IsIn || parameter.IsOut)
  216.             {
  217.                 throw new InvalidOperationException(
  218.                     "Аспект невозможно использовать с методами, " +
  219.                     "обладающими ref-/out-параметрами.");
  220.             }
  221.         }
  222.  
  223.         return true;
  224.     }
  225.  
  226.     MemoCache cacheRoot;
  227.  
  228.     /// <summary>
  229.     /// Обработчик вызова мемоизируемого метода.
  230.     /// </summary>
  231.     public override void OnInvoke(MethodInterceptionArgs args)
  232.     {
  233.         MemoCache argCache = this.cacheRoot;
  234.         Arguments arguments = args.Arguments;
  235.         object result = null;
  236.         int index = 0;
  237.  
  238.     LookupArg: // последовательно извлекаем значения из кэшей
  239.         if (argCache.TryResolve(arguments[index++], out result))
  240.         {
  241.             // если не последний аргумент, то кэш
  242.             if (index < arguments.Count)
  243.             {
  244.                 argCache = (MemoCache) result;
  245.                 goto LookupArg; // да, это goto!
  246.             }
  247.  
  248.             args.ReturnValue = result;
  249.         }
  250.         else // промах кэша, вызываем метод и кэшируем
  251.         {
  252.             args.Proceed();
  253.             argCache.AppendItem(
  254.                 arguments, index - 1, args.ReturnValue);
  255.         }
  256.     }
  257.  
  258.     static Func<MemoCache> RootCacheFactory;
  259.  
  260.     /// <summary>
  261.     /// Статическая инициализация аспекта,
  262.     /// создаёт кэш для мемоизации статических методов.
  263.     /// </summary>
  264.     public override void RuntimeInitialize(MethodBase method)
  265.     {
  266.         var type = GetRootCacheType((MethodInfo) method);
  267.         RootCacheFactory = GetCacheFactory(type);
  268.  
  269.         if (method.IsStatic)
  270.             this.cacheRoot = RootCacheFactory();
  271.  
  272.         base.RuntimeInitialize(method);
  273.     }
  274.  
  275.     /// <summary>
  276.     /// Создание экземпляра аспкета уровня экземпляра,
  277.     /// попадает в конструктор типа, экземплярный метод
  278.     /// которого подвергается аспекту мемоизации.
  279.     /// </summary>
  280.     public object CreateInstance(AdviceArgs adviceArgs)
  281.     {
  282.         return new MemoizeAttribute {
  283.             IsThreadSafe = this.IsThreadSafe
  284.         };
  285.     }
  286.  
  287.     /// <summary>
  288.     /// Инициализация аспекта уровня экземпляра.
  289.     /// </summary>
  290.     public void RuntimeInitializeInstance()
  291.     {
  292.         this.cacheRoot = RootCacheFactory();
  293.     }
  294.  
  295.     #endregion
  296. }
  297.  
  298. class Foo
  299. {
  300.     [Memoize]
  301.     static int StaticFact(int x)
  302.     {
  303.         Console.WriteLine("=> StaticFact({0}) call", x);
  304.  
  305.         if (x == 0) return 1;
  306.         return x * StaticFact(x - 1);
  307.     }
  308.  
  309.     [Memoize(IsThreadSafe=true)]
  310.     int InstanceFact(int x)
  311.     {
  312.         Console.WriteLine("=> InstanceFact({0}) call", x);
  313.  
  314.         return Enumerable
  315.             .Range(1, x)
  316.             .Aggregate(1, (a, b) => a * b);
  317.     }
  318.  
  319.     static void Main()
  320.     {
  321.         Console.WriteLine("SataticFact(2) = {0}", StaticFact(2));
  322.         Console.WriteLine("SataticFact(2) = {0}", StaticFact(2));
  323.         Console.WriteLine("SataticFact(7) = {0}", StaticFact(7));
  324.         Console.WriteLine("SataticFact(7) = {0}", StaticFact(7));
  325.  
  326.         Console.WriteLine();
  327.  
  328.         var a = new Foo();
  329.         var b = new Foo();
  330.  
  331.         Console.WriteLine("a.InstanceFact(4) = {0}", a.InstanceFact(7));
  332.         Console.WriteLine("a.InstanceFact(4) = {0}", a.InstanceFact(7));
  333.  
  334.         Console.WriteLine("b.InstanceFact(2) = {0}", b.InstanceFact(7));
  335.         Console.WriteLine("b.InstanceFact(2) = {0}", b.InstanceFact(7));
  336.     }
  337. }
Advertisement
Add Comment
Please, Sign In to add comment