forked from dotnet/msbuild
/
CopyOnWriteDictionary.cs
408 lines (349 loc) · 12.9 KB
/
CopyOnWriteDictionary.cs
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;
#nullable enable
namespace Microsoft.Build.Collections
{
/// <summary>
/// A dictionary that has copy-on-write semantics.
/// KEYS AND VALUES MUST BE IMMUTABLE OR COPY-ON-WRITE FOR THIS TO WORK.
/// </summary>
/// <typeparam name="V">The value type.</typeparam>
/// <remarks>
/// Thread safety: for all users, this class is as thread safe as the underlying Dictionary implementation, that is,
/// safe for concurrent readers or one writer from EACH user. It achieves this by locking itself and cloning before
/// any write, if it is being shared - i.e., stopping sharing before any writes occur.
/// </remarks>
/// <comment>
/// This class must be serializable as it is used for metadata passed to tasks, which may
/// be run in a separate appdomain.
/// </comment>
[Serializable]
internal class CopyOnWriteDictionary<V> : IDictionary<string, V>, IDictionary, ISerializable
{
#if !NET35 // MSBuildNameIgnoreCaseComparer not compiled into MSBuildTaskHost but also allocations not interesting there.
/// <summary>
/// Empty dictionary with a <see cref="MSBuildNameIgnoreCaseComparer" />,
/// used as the basis of new dictionaries with that comparer to avoid
/// allocating new comparers objects.
/// </summary>
private readonly static ImmutableDictionary<string, V> NameComparerDictionaryPrototype = ImmutableDictionary.Create<string, V>((IEqualityComparer<string>)MSBuildNameIgnoreCaseComparer.Default);
/// <summary>
/// Empty dictionary with <see cref="StringComparer.OrdinalIgnoreCase" />,
/// used as the basis of new dictionaries with that comparer to avoid
/// allocating new comparers objects.
/// </summary>
private readonly static ImmutableDictionary<string, V> OrdinalIgnoreCaseComparerDictionaryPrototype = ImmutableDictionary.Create<string, V>((IEqualityComparer<string>)StringComparer.OrdinalIgnoreCase);
#endif
/// <summary>
/// The backing dictionary.
/// Lazily created.
/// </summary>
private ImmutableDictionary<string, V> _backing;
/// <summary>
/// Constructor. Consider supplying a comparer instead.
/// </summary>
internal CopyOnWriteDictionary()
{
_backing = ImmutableDictionary<string, V>.Empty;
}
/// <summary>
/// Constructor taking an initial capacity
/// </summary>
internal CopyOnWriteDictionary(int capacity)
: this(capacity, null)
{
}
/// <summary>
/// Constructor taking a specified comparer for the keys
/// </summary>
internal CopyOnWriteDictionary(IEqualityComparer<string> keyComparer)
: this(0, keyComparer)
{
}
/// <summary>
/// Constructor taking a specified comparer for the keys and an initial capacity
/// </summary>
internal CopyOnWriteDictionary(int capacity, IEqualityComparer<string>? keyComparer)
{
_backing = GetInitialDictionary(keyComparer);
}
/// <summary>
/// Serialization constructor, for crossing appdomain boundaries
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "context", Justification = "Not needed")]
protected CopyOnWriteDictionary(SerializationInfo info, StreamingContext context)
{
object v = info.GetValue(nameof(_backing), typeof(KeyValuePair<string, V>[]));
object comparer = info.GetValue(nameof(Comparer), typeof(IEqualityComparer<string>));
var b = GetInitialDictionary((IEqualityComparer<string>)comparer);
_backing = b.AddRange((KeyValuePair<string, V>[])v);
}
private static ImmutableDictionary<string, V> GetInitialDictionary(IEqualityComparer<string>? keyComparer)
{
#if NET35
return ImmutableDictionary.Create<string, V>(keyComparer);
#else
return keyComparer is MSBuildNameIgnoreCaseComparer
? NameComparerDictionaryPrototype
: keyComparer == StringComparer.OrdinalIgnoreCase
? OrdinalIgnoreCaseComparerDictionaryPrototype
: ImmutableDictionary.Create<string, V>(keyComparer);
#endif
}
/// <summary>
/// Cloning constructor. Defers the actual clone.
/// </summary>
private CopyOnWriteDictionary(CopyOnWriteDictionary<V> that)
{
_backing = that._backing;
}
public CopyOnWriteDictionary(IDictionary<string, V> dictionary)
{
_backing = dictionary.ToImmutableDictionary();
}
/// <summary>
/// Returns the collection of keys in the dictionary.
/// </summary>
public ICollection<string> Keys => ((IDictionary<string, V>)_backing).Keys;
/// <summary>
/// Returns the collection of values in the dictionary.
/// </summary>
public ICollection<V> Values => ((IDictionary<string, V>)_backing).Values;
/// <summary>
/// Returns the number of items in the collection.
/// </summary>
public int Count => _backing.Count;
/// <summary>
/// Returns true if the collection is read-only.
/// </summary>
public bool IsReadOnly => ((IDictionary<string, V>)_backing).IsReadOnly;
/// <summary>
/// IDictionary implementation
/// </summary>
bool IDictionary.IsFixedSize => false;
/// <summary>
/// IDictionary implementation
/// </summary>
bool IDictionary.IsReadOnly => IsReadOnly;
/// <summary>
/// IDictionary implementation
/// </summary>
ICollection IDictionary.Keys => (ICollection)Keys;
/// <summary>
/// IDictionary implementation
/// </summary>
ICollection IDictionary.Values => (ICollection)Values;
/// <summary>
/// IDictionary implementation
/// </summary>
int ICollection.Count => Count;
/// <summary>
/// IDictionary implementation
/// </summary>
bool ICollection.IsSynchronized => false;
/// <summary>
/// IDictionary implementation
/// </summary>
object ICollection.SyncRoot => this;
/// <summary>
/// Comparer used for keys
/// </summary>
internal IEqualityComparer<string> Comparer
{
get => _backing.KeyComparer;
private set => _backing = _backing.WithComparers(keyComparer: value);
}
/// <summary>
/// Accesses the value for the specified key.
/// </summary>
public V this[string key]
{
get => _backing[key];
set
{
_backing = _backing.SetItem(key, value);
}
}
#nullable disable
/// <summary>
/// IDictionary implementation
/// </summary>
object IDictionary.this[object key]
{
get
{
TryGetValue((string) key, out V val);
return val;
}
set => this[(string)key] = (V)value;
}
#nullable restore
/// <summary>
/// Adds a value to the dictionary.
/// </summary>
public void Add(string key, V value)
{
_backing = _backing.SetItem(key, value);
}
/// <summary>
/// Adds several value to the dictionary.
/// </summary>
public void SetItems(IEnumerable<KeyValuePair<string, V>> items)
{
_backing = _backing.SetItems(items);
}
public IEnumerable<KeyValuePair<string, V>> Where(Func<KeyValuePair<string, V>, bool> predicate)
{
return _backing.Where(predicate);
}
/// <summary>
/// Returns true if the dictionary contains the specified key.
/// </summary>
public bool ContainsKey(string key)
{
return _backing.ContainsKey(key);
}
/// <summary>
/// Removes the entry for the specified key from the dictionary.
/// </summary>
public bool Remove(string key)
{
ImmutableDictionary<string, V> initial = _backing;
_backing = _backing.Remove(key);
return initial != _backing; // whether the removal occured
}
/// <summary>
/// Attempts to find the value for the specified key in the dictionary.
/// </summary>
public bool TryGetValue(string key, out V value)
{
return _backing.TryGetValue(key, out value);
}
/// <summary>
/// Adds an item to the collection.
/// </summary>
public void Add(KeyValuePair<string, V> item)
{
_backing = _backing.SetItem(item.Key, item.Value);
}
/// <summary>
/// Clears the collection.
/// </summary>
public void Clear()
{
_backing = _backing.Clear();
}
/// <summary>
/// Returns true ff the collection contains the specified item.
/// </summary>
public bool Contains(KeyValuePair<string, V> item)
{
return _backing.Contains(item);
}
/// <summary>
/// Copies all of the elements of the collection to the specified array.
/// </summary>
public void CopyTo(KeyValuePair<string, V>[] array, int arrayIndex)
{
((IDictionary<string, V>)_backing).CopyTo(array, arrayIndex);
}
/// <summary>
/// Remove an item from the dictionary.
/// </summary>
public bool Remove(KeyValuePair<string, V> item)
{
ImmutableDictionary<string, V> initial = _backing;
_backing = _backing.Remove(item.Key);
return initial != _backing; // whether the removal occured
}
/// <summary>
/// Implementation of generic IEnumerable.GetEnumerator()
/// </summary>
public IEnumerator<KeyValuePair<string, V>> GetEnumerator()
{
return _backing.GetEnumerator();
}
/// <summary>
/// Implementation of IEnumerable.GetEnumerator()
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<KeyValuePair<string, V>>)this).GetEnumerator();
}
/// <summary>
/// IDictionary implementation.
/// </summary>
void IDictionary.Add(object key, object value)
{
Add((string)key, (V)value);
}
/// <summary>
/// IDictionary implementation.
/// </summary>
void IDictionary.Clear()
{
Clear();
}
/// <summary>
/// IDictionary implementation.
/// </summary>
bool IDictionary.Contains(object key)
{
return ContainsKey((string)key);
}
/// <summary>
/// IDictionary implementation.
/// </summary>
IDictionaryEnumerator IDictionary.GetEnumerator()
{
return ((IDictionary)_backing).GetEnumerator();
}
/// <summary>
/// IDictionary implementation.
/// </summary>
void IDictionary.Remove(object key)
{
Remove((string)key);
}
/// <summary>
/// IDictionary implementation.
/// </summary>
void ICollection.CopyTo(Array array, int index)
{
int i = 0;
foreach (KeyValuePair<string, V> entry in this)
{
array.SetValue(new DictionaryEntry(entry.Key, entry.Value), index + i);
i++;
}
}
/// <summary>
/// Clone, with the actual clone deferred
/// </summary>
internal CopyOnWriteDictionary<V> Clone()
{
return new CopyOnWriteDictionary<V>(this);
}
/// <summary>
/// Returns true if these dictionaries have the same backing.
/// </summary>
internal bool HasSameBacking(CopyOnWriteDictionary<V> other)
{
return ReferenceEquals(other._backing, _backing);
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
ImmutableDictionary<string, V> snapshot = _backing;
KeyValuePair<string, V>[] array = snapshot.ToArray();
info.AddValue(nameof(_backing), array);
info.AddValue(nameof(Comparer), Comparer);
}
}
}