AOP — FIFO Memoizer with PostSharp

Back in my first post on AOP, I men­tioned the Mem­o­iz­er on D. Patrick Caldwell’s blog, well, today I came across a sit­u­a­tion where I was able to use it but first I need­ed to make a few mod­i­fi­ca­tions because the orig­i­nal imple­men­ta­tion didn’t sat­is­fy some of my require­ments:

  • There is no cap on the size of the dic­tio­nary, I want to avoid a sit­u­a­tions where my appli­ca­tion uses too much mem­o­ry, and in the extreme case throws a Out­OfMem­o­ryEx­cep­tion;
  • There is one sta­t­ic cache shared across all meth­ods, so even with a cap on the size of the dic­tio­nary it won’t stop one method from tak­ing up all the avail­able spaces in the dic­tio­nary
  • The order of the mem­os is not kept, so you won’t be able to imple­ment have FIFO strat­e­gy for remov­ing old memo entries

In case you’re won­der­ing why I would require these func­tion­al­i­ties, I’m build­ing an image view­er with zip sup­port and it doesn’t make sense to load all the images in the zip file into mem­o­ry at the start (the zip files can be typ­i­cal­ly hun­dreds of megs big).

The images are sort­ed, and as you’re nav­i­gat­ing through the images it behaves like a LinkedList any­way, so I’m imple­ment­ing a slid­ing win­dow based on where you are in the list and load the pre­vi­ous and next 10 images into cache. There­fore a FIFO mem­o­iz­er can help reduce the amount of reads from the zip file I need to per­form (decom­pres­sion is an expen­sive oper­a­tion).

And here’s the mod­i­fied ver­sion of the Mem­o­iz­er attribute:

[AttributeUsage(AttributeTargets.Method)] // only allowed on methods
public sealed class MemoizeAttribute : OnMethodInvocationAspect
    private const int DefaultMemoSize = 100; // default memo size is 100
    // private field to store memos
    private readonly Dictionary<string, object> _memos = new Dictionary<string, object>();
    // private queue to keep track of the order the memos are put in
    private readonly Queue<string> _queue = new Queue<string>();

    #region Constructors
    public MemoizeAttribute() : this(DefaultMemoSize)
    public MemoizeAttribute(int memoSize)
        MemoSize = memoSize;

    public int MemoSize { get; set; } // how many items to keep in the memo

    // intercept the method invocation
    public override void OnInvocation(MethodInvocationEventArgs eventArgs)
        // get the arguments that were passed to the method
        var args = eventArgs.GetArgumentArray();
        var keyBuilder = new StringBuilder();
        // append the hashcode of each arg to the key
        // this limits us to value types (and strings)
        // i need a better way to do this (and preferably
        // a faster one)
        for (var i = 0; i < args.Length; i++)
        var key = keyBuilder.ToString();
        // if the key doesn't exist, invoke the original method
        // passing the original arguments and store the result
        if (!_memos.ContainsKey(key))
            _memos[key] = eventArgs.Method.Invoke(eventArgs.Instance, args);
            // if we've exceeded the set memo size, then remove the earliest entry
            if (_queue.Count > MemoSize)
                var deQueueKey = _queue.Dequeue();
        // return the memo
        eventArgs.ReturnValue = _memos[key];

And here’s how you use it, remem­ber, the size cap applies to all the calls to this method (not lim­it­ed to a par­tic­u­lar instance):

private MemoryStream GetImageStream(ZipFile zipFile, string fileName)
    using (var memoryStream = new MemoryStream())
        return memoryStream;