536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
149 lines
5.2 KiB
C#
149 lines
5.2 KiB
C#
//---------------------------------------------------------------------
|
|
// <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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|