2 // ConditionalWeakTable.cs
5 // Rodrigo Kumpera (rkumpera@novell.com)
7 // Copyright (C) 2010 Novell, Inc (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections;
32 namespace System.Runtime.CompilerServices
34 internal struct Ephemeron
37 internal object value;
42 The runtime need to inform the table about how many entries were expired.
43 Compact the table when there are too many tombstones.
44 Rehash to a smaller size when there are too few entries.
45 Change rehash condition check to use non-fp code.
46 Look into using quatratic probing/double hashing to reduce clustering problems.
47 Make reads and non-expanding writes (add/remove) lock free.
49 public sealed class ConditionalWeakTable<TKey, TValue>
53 const int INITIAL_SIZE = 13;
54 const float LOAD_FACTOR = 0.7f;
57 object _lock = new object ();
60 public delegate TValue CreateValueCallback (TKey key);
62 public ConditionalWeakTable ()
64 data = new Ephemeron [INITIAL_SIZE];
65 GC.register_ephemeron_array (data);
68 /*LOCKING: _lock must be held*/
70 uint newSize = (uint)HashPrimeNumbers.ToPrime ((data.Length << 1) | 1);
71 //Console.WriteLine ("--- resizing from {0} to {1}", data.Length, newSize);
73 Ephemeron[] tmp = new Ephemeron [newSize];
74 GC.register_ephemeron_array (tmp);
77 for (int i = 0; i < data.Length; ++i) {
78 object key = data[i].key;
79 object value = data[i].value;
80 if (key == null || key == GC.EPHEMERON_TOMBSTONE)
87 idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
90 object k = tmp [idx].key;
92 //keys might be GC'd during Rehash
93 if (k == null || k == GC.EPHEMERON_TOMBSTONE) {
98 if (++idx == len) //Wrap around
100 } while (idx != initial_idx);
102 tmp [free_slot].key = key;
103 tmp [free_slot].value = value;
110 public void Add (TKey key, TValue value)
112 if (key == default (TKey))
113 throw new ArgumentNullException ("Null key", "key");
116 if (size >= data.Length * LOAD_FACTOR)
119 int len = data.Length;
123 idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
125 object k = data [idx].key;
131 } else if (k == GC.EPHEMERON_TOMBSTONE && free_slot == -1) { //Add requires us to check for dupes :(
133 } else if (k == key) {
134 throw new ArgumentException ("Key already in the list", "key");
137 if (++idx == len) //Wrap around
139 } while (idx != initial_idx);
141 data [free_slot].key = key;
142 data [free_slot].value = value;
147 public bool Remove (TKey key)
149 if (key == default (TKey))
150 throw new ArgumentNullException ("Null key", "key");
153 int len = data.Length;
154 int idx, initial_idx;
155 idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
157 object k = data[idx].key;
159 data [idx].key = GC.EPHEMERON_TOMBSTONE;
160 data [idx].value = null;
166 if (++idx == len) //Wrap around
168 } while (idx != initial_idx);
173 public bool TryGetValue (TKey key, out TValue value)
176 throw new ArgumentNullException ("Null key", "key");
178 value = default (TValue);
180 int len = data.Length;
181 int idx, initial_idx;
182 idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
185 object k = data [idx].key;
187 value = (TValue)data [idx].value;
192 if (++idx == len) //Wrap around
194 } while (idx != initial_idx);
199 public TValue GetOrCreateValue (TKey key)
201 return GetValue (key, k => Activator.CreateInstance<TValue> ());
204 public TValue GetValue (TKey key, CreateValueCallback createValueCallback)
206 if (createValueCallback == null)
207 throw new ArgumentNullException ("Null create delegate", "createValueCallback");
212 if (TryGetValue (key, out res))
215 res = createValueCallback (key);