File: Memoizer.cs

package info (click to toggle)
mono 6.14.1%2Bds2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,282,732 kB
  • sloc: cs: 11,182,461; xml: 2,850,281; ansic: 699,123; cpp: 122,919; perl: 58,604; javascript: 30,841; asm: 21,845; makefile: 19,602; sh: 10,973; python: 4,772; pascal: 925; sql: 859; sed: 16; php: 1
file content (148 lines) | stat: -rw-r--r-- 5,297 bytes parent folder | download | duplicates (6)
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;
                }
            }
        }
    }
}