2 // ConditionalWeakTable.cs
5 // Rodrigo Kumpera (rkumpera@novell.com)
6 // Tautvydas Žilys <zilys@unity3d.com>
8 // Copyright (C) 2010 Novell, Inc (http://www.novell.com)
9 // Copyright (C) 2016 Unity Technologies (https://unity3d.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Collections;
33 using System.Collections.Generic;
35 namespace System.Runtime.CompilerServices
37 internal struct Ephemeron
40 internal object value;
45 The runtime need to inform the table about how many entries were expired.
46 Compact the table when there are too many tombstones.
47 Rehash to a smaller size when there are too few entries.
48 Change rehash condition check to use non-fp code.
49 Look into using quatratic probing/double hashing to reduce clustering problems.
50 Make reads and non-expanding writes (add/remove) lock free.
52 public sealed class ConditionalWeakTable<TKey, TValue>
56 const int INITIAL_SIZE = 13;
57 const float LOAD_FACTOR = 0.7f;
60 object _lock = new object ();
63 public delegate TValue CreateValueCallback (TKey key);
65 public ConditionalWeakTable ()
67 data = new Ephemeron [INITIAL_SIZE];
68 GC.register_ephemeron_array (data);
71 ~ConditionalWeakTable ()
75 /*LOCKING: _lock must be held*/
77 uint newSize = (uint)HashHelpers.GetPrime ((data.Length << 1) | 1);
78 //Console.WriteLine ("--- resizing from {0} to {1}", data.Length, newSize);
80 Ephemeron[] tmp = new Ephemeron [newSize];
81 GC.register_ephemeron_array (tmp);
84 for (int i = 0; i < data.Length; ++i) {
85 object key = data[i].key;
86 object value = data[i].value;
87 if (key == null || key == GC.EPHEMERON_TOMBSTONE)
94 idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
97 object k = tmp [idx].key;
99 //keys might be GC'd during Rehash
100 if (k == null || k == GC.EPHEMERON_TOMBSTONE) {
105 if (++idx == len) //Wrap around
107 } while (idx != initial_idx);
109 tmp [free_slot].key = key;
110 tmp [free_slot].value = value;
117 public void Add (TKey key, TValue value)
119 if (key == default (TKey))
120 throw new ArgumentNullException ("Null key", "key");
123 if (size >= data.Length * LOAD_FACTOR)
126 int len = data.Length;
130 idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
132 object k = data [idx].key;
138 } else if (k == GC.EPHEMERON_TOMBSTONE && free_slot == -1) { //Add requires us to check for dupes :(
140 } else if (k == key) {
141 throw new ArgumentException ("Key already in the list", "key");
144 if (++idx == len) //Wrap around
146 } while (idx != initial_idx);
148 data [free_slot].key = key;
149 data [free_slot].value = value;
154 public bool Remove (TKey key)
156 if (key == default (TKey))
157 throw new ArgumentNullException ("Null key", "key");
160 int len = data.Length;
161 int idx, initial_idx;
162 idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
164 object k = data[idx].key;
166 data [idx].key = GC.EPHEMERON_TOMBSTONE;
167 data [idx].value = null;
173 if (++idx == len) //Wrap around
175 } while (idx != initial_idx);
180 public bool TryGetValue (TKey key, out TValue value)
183 throw new ArgumentNullException ("Null key", "key");
185 value = default (TValue);
187 int len = data.Length;
188 int idx, initial_idx;
189 idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
192 object k = data [idx].key;
194 value = (TValue)data [idx].value;
199 if (++idx == len) //Wrap around
201 } while (idx != initial_idx);
206 public TValue GetOrCreateValue (TKey key)
208 return GetValue (key, k => Activator.CreateInstance<TValue> ());
211 public TValue GetValue (TKey key, CreateValueCallback createValueCallback)
213 if (createValueCallback == null)
214 throw new ArgumentNullException ("Null create delegate", "createValueCallback");
219 if (TryGetValue (key, out res))
222 res = createValueCallback (key);
229 //--------------------------------------------------------------------------------------------
230 // Find a key that equals (value equality) with the given key - don't use in perf critical path
231 // Note that it calls out to Object.Equals which may calls the override version of Equals
232 // and that may take locks and leads to deadlock
233 // Currently it is only used by WinRT event code and you should only use this function
234 // if you know for sure that either you won't run into dead locks or you need to live with the
236 //--------------------------------------------------------------------------------------------
237 [System.Security.SecuritySafeCritical]
238 [FriendAccessAllowed]
239 internal TKey FindEquivalentKeyUnsafe(TKey key, out TValue value)
243 for (int i = 0; i < data.Length; ++i)
246 if (Object.Equals(item.key, key))
248 value = (TValue)item.value;
249 return (TKey)item.key;
254 value = default(TValue);
258 //--------------------------------------------------------------------------------------------
259 // Clear all the key/value pairs
260 //--------------------------------------------------------------------------------------------
261 [System.Security.SecuritySafeCritical]
262 internal void Clear()
266 for (int i = 0; i < data.Length; i++)
268 data[i].key = GC.EPHEMERON_TOMBSTONE;
269 data[i].value = null;
276 // extracted from ../../../../external/referencesource/mscorlib/system/runtime/compilerservices/
277 internal ICollection<TKey> Keys
279 [System.Security.SecuritySafeCritical]
282 var tombstone = GC.EPHEMERON_TOMBSTONE;
283 List<TKey> list = new List<TKey>(data.Length);
286 for (int i = 0; i < data.Length; ++i)
288 TKey key = (TKey) data [i].key;
289 if (key != null && key != tombstone)
297 internal ICollection<TValue> Values
299 [System.Security.SecuritySafeCritical]
302 var tombstone = GC.EPHEMERON_TOMBSTONE;
303 List<TValue> list = new List<TValue>(data.Length);
306 for (int i = 0; i < data.Length; ++i)
309 if (item.key != null && item.key != tombstone)
310 list.Add((TValue)item.value);