1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
//---------------------------------------------------------------------
// <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;
}
}
}
}
}
|