File: System\Data\Common\Utils\Memoizer.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="Memoizer.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner  	 Microsoft, Microsoft
//---------------------------------------------------------------------
 
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
namespace System.Data.Common.Utils
{
    /// <summary>
    /// Remembers the result of evaluating an expensive function so that subsequent
    /// evaluations are faster. Thread-safe.
    /// </summary>
    /// <typeparam name="TArg">Type of the argument to the function.</typeparam>
    /// <typeparam name="TResult">Type of the function result.</typeparam>
    internal sealed class Memoizer<TArg, TResult>
    {
        private readonly Func<TArg, TResult> _function;
        private readonly Dictionary<TArg, Result> _resultCache;
        private readonly ReaderWriterLockSlim _lock;
 
        /// <summary>
        /// Constructs
        /// </summary>
        /// <param name="function">Required. Function whose values are being cached.</param>
        /// <param name="argComparer">Optional. Comparer used to determine if two functions arguments
        /// are the same.</param>
        internal Memoizer(Func<TArg, TResult> function, IEqualityComparer<TArg> argComparer)
        {
            EntityUtil.CheckArgumentNull(function, "function");
 
            _function = function;
            _resultCache = new Dictionary<TArg, Result>(argComparer);
            _lock = new ReaderWriterLockSlim();
        }
 
        /// <summary>
        /// Evaluates the wrapped function for the given argument. If the function has already
        /// been evaluated for the given argument, returns cached value. Otherwise, the value
        /// is computed and returned.
        /// </summary>
        /// <param name="arg">Function argument.</param>
        /// <returns>Function result.</returns>
        internal TResult Evaluate(TArg arg)
        {
            Result result;
 
            // Check to see if a result has already been computed
            if (!TryGetResult(arg, out result))
            {
                // compute the new value
                _lock.EnterWriteLock();
                try
                {
                    // see if the value has been computed in the interim
                    if (!_resultCache.TryGetValue(arg, out result))
                    {
                        result = new Result(() => _function(arg));
                        _resultCache.Add(arg, result);
                    }
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
            }
 
            // note: you need to release the global cache lock before (potentially) acquiring
            // a result lock in result.GetValue()
            return result.GetValue();
        }
 
        internal bool TryGetValue(TArg arg, out TResult value)
        {
            Result result;
            if (TryGetResult(arg, out result))
            {
                value = result.GetValue();
                return true;
            }
            else
            {
                value = default(TResult);
                return false;
            }
        }
 
        private bool TryGetResult(TArg arg, out Result result)
        {
            _lock.EnterReadLock();
            try
            {
                return _resultCache.TryGetValue(arg, out result);
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }
 
        /// <summary>
        /// Encapsulates a 'deferred' result. The result is constructed with a delegate (must not 
        /// be null) and when the user requests a value the delegate is invoked and stored.
        /// </summary>
        private class Result
        {
            private TResult _value;
            private Func<TResult> _delegate;
 
            internal Result(Func<TResult> createValueDelegate)
            {
                Debug.Assert(null != createValueDelegate, "delegate must be given");
                _delegate = createValueDelegate;
            }
 
            internal TResult GetValue()
            {
                if (null == _delegate)
                {
                    // if the delegate has been cleared, it means we have already computed the value
                    return _value;
                }
 
                // lock the entry while computing the value so that two threads
                // don't simultaneously do the work
                lock (this)
                {
                    if (null == _delegate)
                    {
                        // between our initial check and our acquisition of the lock, some other
                        // thread may have computed the value
                        return _value;
                    }
                    _value = _delegate();
 
                    // ensure _delegate (and its closure) is garbage collected, and set to null
                    // to indicate that the value has been computed
                    _delegate = null;
                    return _value;
                }
            }
        }
    }
}