84395cf4cd78d9785bcf26696418a56bd46163d0
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Common / Utils / Memoizer.cs
1 //---------------------------------------------------------------------
2 // <copyright file="Memoizer.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner        Microsoft, Microsoft
7 //---------------------------------------------------------------------
8
9 using System.Collections.Generic;
10 using System.Threading;
11 using System.Diagnostics;
12 namespace System.Data.Common.Utils
13 {
14     /// <summary>
15     /// Remembers the result of evaluating an expensive function so that subsequent
16     /// evaluations are faster. Thread-safe.
17     /// </summary>
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>
21     {
22         private readonly Func<TArg, TResult> _function;
23         private readonly Dictionary<TArg, Result> _resultCache;
24         private readonly ReaderWriterLockSlim _lock;
25
26         /// <summary>
27         /// Constructs
28         /// </summary>
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)
33         {
34             EntityUtil.CheckArgumentNull(function, "function");
35
36             _function = function;
37             _resultCache = new Dictionary<TArg, Result>(argComparer);
38             _lock = new ReaderWriterLockSlim();
39         }
40
41         /// <summary>
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.
45         /// </summary>
46         /// <param name="arg">Function argument.</param>
47         /// <returns>Function result.</returns>
48         internal TResult Evaluate(TArg arg)
49         {
50             Result result;
51
52             // Check to see if a result has already been computed
53             if (!TryGetResult(arg, out result))
54             {
55                 // compute the new value
56                 _lock.EnterWriteLock();
57                 try
58                 {
59                     // see if the value has been computed in the interim
60                     if (!_resultCache.TryGetValue(arg, out result))
61                     {
62                         result = new Result(() => _function(arg));
63                         _resultCache.Add(arg, result);
64                     }
65                 }
66                 finally
67                 {
68                     _lock.ExitWriteLock();
69                 }
70             }
71
72             // note: you need to release the global cache lock before (potentially) acquiring
73             // a result lock in result.GetValue()
74             return result.GetValue();
75         }
76
77         internal bool TryGetValue(TArg arg, out TResult value)
78         {
79             Result result;
80             if (TryGetResult(arg, out result))
81             {
82                 value = result.GetValue();
83                 return true;
84             }
85             else
86             {
87                 value = default(TResult);
88                 return false;
89             }
90         }
91
92         private bool TryGetResult(TArg arg, out Result result)
93         {
94             _lock.EnterReadLock();
95             try
96             {
97                 return _resultCache.TryGetValue(arg, out result);
98             }
99             finally
100             {
101                 _lock.ExitReadLock();
102             }
103         }
104
105         /// <summary>
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.
108         /// </summary>
109         private class Result
110         {
111             private TResult _value;
112             private Func<TResult> _delegate;
113
114             internal Result(Func<TResult> createValueDelegate)
115             {
116                 Debug.Assert(null != createValueDelegate, "delegate must be given");
117                 _delegate = createValueDelegate;
118             }
119
120             internal TResult GetValue()
121             {
122                 if (null == _delegate)
123                 {
124                     // if the delegate has been cleared, it means we have already computed the value
125                     return _value;
126                 }
127
128                 // lock the entry while computing the value so that two threads
129                 // don't simultaneously do the work
130                 lock (this)
131                 {
132                     if (null == _delegate)
133                     {
134                         // between our initial check and our acquisition of the lock, some other
135                         // thread may have computed the value
136                         return _value;
137                     }
138                     _value = _delegate();
139
140                     // ensure _delegate (and its closure) is garbage collected, and set to null
141                     // to indicate that the value has been computed
142                     _delegate = null;
143                     return _value;
144                 }
145             }
146         }
147     }
148 }