|
//---------------------------------------------------------------------
// <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;
}
}
}
}
}
|