1 //---------------------------------------------------------------------
2 // <copyright file="Memoizer.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // @owner Microsoft, Microsoft
7 //---------------------------------------------------------------------
9 using System.Collections.Generic;
10 using System.Threading;
11 using System.Diagnostics;
12 namespace System.Data.Common.Utils
15 /// Remembers the result of evaluating an expensive function so that subsequent
16 /// evaluations are faster. Thread-safe.
18 /// <typeparam name="TArg">Type of the argument to the function.</typeparam>
19 /// <typeparam name="TResult">Type of the function result.</typeparam>
20 internal sealed class Memoizer<TArg, TResult>
22 private readonly Func<TArg, TResult> _function;
23 private readonly Dictionary<TArg, Result> _resultCache;
24 private readonly ReaderWriterLockSlim _lock;
29 /// <param name="function">Required. Function whose values are being cached.</param>
30 /// <param name="argComparer">Optional. Comparer used to determine if two functions arguments
31 /// are the same.</param>
32 internal Memoizer(Func<TArg, TResult> function, IEqualityComparer<TArg> argComparer)
34 EntityUtil.CheckArgumentNull(function, "function");
37 _resultCache = new Dictionary<TArg, Result>(argComparer);
38 _lock = new ReaderWriterLockSlim();
42 /// Evaluates the wrapped function for the given argument. If the function has already
43 /// been evaluated for the given argument, returns cached value. Otherwise, the value
44 /// is computed and returned.
46 /// <param name="arg">Function argument.</param>
47 /// <returns>Function result.</returns>
48 internal TResult Evaluate(TArg arg)
52 // Check to see if a result has already been computed
53 if (!TryGetResult(arg, out result))
55 // compute the new value
56 _lock.EnterWriteLock();
59 // see if the value has been computed in the interim
60 if (!_resultCache.TryGetValue(arg, out result))
62 result = new Result(() => _function(arg));
63 _resultCache.Add(arg, result);
68 _lock.ExitWriteLock();
72 // note: you need to release the global cache lock before (potentially) acquiring
73 // a result lock in result.GetValue()
74 return result.GetValue();
77 internal bool TryGetValue(TArg arg, out TResult value)
80 if (TryGetResult(arg, out result))
82 value = result.GetValue();
87 value = default(TResult);
92 private bool TryGetResult(TArg arg, out Result result)
94 _lock.EnterReadLock();
97 return _resultCache.TryGetValue(arg, out result);
101 _lock.ExitReadLock();
106 /// Encapsulates a 'deferred' result. The result is constructed with a delegate (must not
107 /// be null) and when the user requests a value the delegate is invoked and stored.
111 private TResult _value;
112 private Func<TResult> _delegate;
114 internal Result(Func<TResult> createValueDelegate)
116 Debug.Assert(null != createValueDelegate, "delegate must be given");
117 _delegate = createValueDelegate;
120 internal TResult GetValue()
122 if (null == _delegate)
124 // if the delegate has been cleared, it means we have already computed the value
128 // lock the entry while computing the value so that two threads
129 // don't simultaneously do the work
132 if (null == _delegate)
134 // between our initial check and our acquisition of the lock, some other
135 // thread may have computed the value
138 _value = _delegate();
140 // ensure _delegate (and its closure) is garbage collected, and set to null
141 // to indicate that the value has been computed