diff --git a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder+PrivateMarshal.cs b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder+PrivateMarshal.cs
index f2d3c6cff5f6e..01f7a1c796de2 100644
--- a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder+PrivateMarshal.cs
+++ b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder+PrivateMarshal.cs
@@ -15,7 +15,7 @@ internal static class PrivateMarshal
{
///
public static ref TValue FindValue(Builder dictionary, TKey key)
- => ref SegmentedCollectionsMarshal.GetValueRefOrNullRef(dictionary.GetOrCreateMutableDictionary(), key);
+ => ref SegmentedCollectionsMarshal.GetValueRefOrNullRef(dictionary._builder.GetOrCreateMutableDictionary(), key);
}
}
}
diff --git a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder.cs b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder.cs
index c813f54169bb4..6e36d573d4a10 100644
--- a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder.cs
+++ b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder.cs
@@ -6,6 +6,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis.Collections.Internal;
namespace Microsoft.CodeAnalysis.Collections
{
@@ -14,50 +15,26 @@ internal readonly partial struct ImmutableSegmentedDictionary
public sealed partial class Builder : IDictionary, IReadOnlyDictionary, IDictionary
{
///
- /// The immutable collection this builder is based on.
+ /// The private builder implementation.
///
- private ImmutableSegmentedDictionary _dictionary;
-
- ///
- /// The current mutable collection this builder is operating on. This field is initialized to a copy of
- /// the first time a change is made.
- ///
- private SegmentedDictionary? _mutableDictionary;
+ private ValueBuilder _builder;
internal Builder(ImmutableSegmentedDictionary dictionary)
- {
- _dictionary = dictionary;
- }
+ => _builder = new ValueBuilder(dictionary);
public IEqualityComparer KeyComparer
{
- get
- {
- return ReadOnlyDictionary.Comparer;
- }
-
- set
- {
- if (value is null)
- throw new ArgumentNullException(nameof(value));
-
- if (value != KeyComparer)
- {
- // Rewrite the mutable dictionary using a new comparer
- var valuesToAdd = ReadOnlyDictionary;
- _mutableDictionary = new SegmentedDictionary(value);
- AddRange(valuesToAdd);
- }
- }
+ get => _builder.KeyComparer;
+ set => _builder.KeyComparer = value;
}
- public int Count => ReadOnlyDictionary.Count;
+ public int Count => _builder.Count;
public KeyCollection Keys => new(this);
public ValueCollection Values => new(this);
- private SegmentedDictionary ReadOnlyDictionary => _mutableDictionary ?? _dictionary._dictionary;
+ private SegmentedDictionary ReadOnlyDictionary => _builder.ReadOnlyDictionary;
IEnumerable IReadOnlyDictionary.Keys => Keys;
@@ -67,181 +44,105 @@ public IEqualityComparer KeyComparer
ICollection IDictionary.Values => Values;
- bool ICollection>.IsReadOnly => false;
+ bool ICollection>.IsReadOnly => ICollectionCalls>.IsReadOnly(ref _builder);
ICollection IDictionary.Keys => Keys;
ICollection IDictionary.Values => Values;
- bool IDictionary.IsReadOnly => false;
+ bool IDictionary.IsReadOnly => IDictionaryCalls.IsReadOnly(ref _builder);
- bool IDictionary.IsFixedSize => false;
+ bool IDictionary.IsFixedSize => IDictionaryCalls.IsFixedSize(ref _builder);
object ICollection.SyncRoot => this;
- bool ICollection.IsSynchronized => false;
+ bool ICollection.IsSynchronized => ICollectionCalls.IsSynchronized(ref _builder);
public TValue this[TKey key]
{
- get => ReadOnlyDictionary[key];
- set => GetOrCreateMutableDictionary()[key] = value;
+ get => _builder[key];
+ set => _builder[key] = value;
}
object? IDictionary.this[object key]
{
- get => ((IDictionary)ReadOnlyDictionary)[key];
- set => ((IDictionary)GetOrCreateMutableDictionary())[key] = value;
- }
-
- private SegmentedDictionary GetOrCreateMutableDictionary()
- {
- return _mutableDictionary ??= new SegmentedDictionary(_dictionary._dictionary, _dictionary.KeyComparer);
+ get => IDictionaryCalls.GetItem(ref _builder, key);
+ set => IDictionaryCalls.SetItem(ref _builder, key, value);
}
public void Add(TKey key, TValue value)
- {
- if (Contains(new KeyValuePair(key, value)))
- return;
-
- GetOrCreateMutableDictionary().Add(key, value);
- }
+ => _builder.Add(key, value);
public void Add(KeyValuePair item)
- => Add(item.Key, item.Value);
+ => _builder.Add(item);
public void AddRange(IEnumerable> items)
- {
- if (items == null)
- throw new ArgumentNullException(nameof(items));
-
- foreach (var pair in items)
- Add(pair.Key, pair.Value);
- }
+ => _builder.AddRange(items);
public void Clear()
- {
- if (ReadOnlyDictionary.Count != 0)
- {
- if (_mutableDictionary is null)
- _mutableDictionary = new SegmentedDictionary(KeyComparer);
- else
- _mutableDictionary.Clear();
- }
- }
+ => _builder.Clear();
public bool Contains(KeyValuePair item)
- {
- return TryGetValue(item.Key, out var value)
- && EqualityComparer.Default.Equals(value, item.Value);
- }
+ => _builder.Contains(item);
public bool ContainsKey(TKey key)
- => ReadOnlyDictionary.ContainsKey(key);
+ => _builder.ContainsKey(key);
public bool ContainsValue(TValue value)
- {
- return _dictionary.ContainsValue(value);
- }
+ => _builder.ContainsValue(value);
public Enumerator GetEnumerator()
- => new(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair);
+ => _builder.GetEnumerator();
public TValue? GetValueOrDefault(TKey key)
- {
- if (TryGetValue(key, out var value))
- return value;
-
- return default;
- }
+ => _builder.GetValueOrDefault(key);
public TValue GetValueOrDefault(TKey key, TValue defaultValue)
- {
- if (TryGetValue(key, out var value))
- return value;
-
- return defaultValue;
- }
+ => _builder.GetValueOrDefault(key, defaultValue);
public bool Remove(TKey key)
- {
- if (_mutableDictionary is null && !ContainsKey(key))
- return false;
-
- return GetOrCreateMutableDictionary().Remove(key);
- }
+ => _builder.Remove(key);
public bool Remove(KeyValuePair item)
- {
- if (!Contains(item))
- {
- return false;
- }
-
- GetOrCreateMutableDictionary().Remove(item.Key);
- return true;
- }
+ => _builder.Remove(item);
public void RemoveRange(IEnumerable keys)
- {
- if (keys is null)
- throw new ArgumentNullException(nameof(keys));
-
- foreach (var key in keys)
- {
- Remove(key);
- }
- }
+ => _builder.RemoveRange(keys);
public bool TryGetKey(TKey equalKey, out TKey actualKey)
- {
- foreach (var key in Keys)
- {
- if (KeyComparer.Equals(key, equalKey))
- {
- actualKey = key;
- return true;
- }
- }
-
- actualKey = equalKey;
- return false;
- }
+ => _builder.TryGetKey(equalKey, out actualKey);
#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
- => ReadOnlyDictionary.TryGetValue(key, out value);
+ => _builder.TryGetValue(key, out value);
public ImmutableSegmentedDictionary ToImmutable()
- {
- _dictionary = new ImmutableSegmentedDictionary(ReadOnlyDictionary);
- _mutableDictionary = null;
- return _dictionary;
- }
+ => _builder.ToImmutable();
void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
- => ((ICollection>)ReadOnlyDictionary).CopyTo(array, arrayIndex);
+ => ICollectionCalls>.CopyTo(ref _builder, array, arrayIndex);
IEnumerator> IEnumerable>.GetEnumerator()
- => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair);
+ => IEnumerableCalls>.GetEnumerator(ref _builder);
IEnumerator IEnumerable.GetEnumerator()
- => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair);
+ => IEnumerableCalls.GetEnumerator(ref _builder);
bool IDictionary.Contains(object key)
- => ((IDictionary)ReadOnlyDictionary).Contains(key);
+ => IDictionaryCalls.Contains(ref _builder, key);
void IDictionary.Add(object key, object? value)
- => ((IDictionary)GetOrCreateMutableDictionary()).Add(key, value);
+ => IDictionaryCalls.Add(ref _builder, key, value);
IDictionaryEnumerator IDictionary.GetEnumerator()
- => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.DictionaryEntry);
+ => IDictionaryCalls.GetEnumerator(ref _builder);
void IDictionary.Remove(object key)
- => ((IDictionary)GetOrCreateMutableDictionary()).Remove(key);
+ => IDictionaryCalls.Remove(ref _builder, key);
void ICollection.CopyTo(Array array, int index)
- => ((ICollection)ReadOnlyDictionary).CopyTo(array, index);
+ => ICollectionCalls.CopyTo(ref _builder, array, index);
internal TestAccessor GetTestAccessor()
=> new TestAccessor(this);
@@ -249,7 +150,7 @@ internal TestAccessor GetTestAccessor()
internal readonly struct TestAccessor(Builder instance)
{
internal SegmentedDictionary GetOrCreateMutableDictionary()
- => instance.GetOrCreateMutableDictionary();
+ => instance._builder.GetOrCreateMutableDictionary();
}
}
}
diff --git a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+ValueBuilder.cs b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+ValueBuilder.cs
new file mode 100644
index 0000000000000..4c20bfdc65cbb
--- /dev/null
+++ b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+ValueBuilder.cs
@@ -0,0 +1,259 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.CodeAnalysis.Collections
+{
+ internal readonly partial struct ImmutableSegmentedDictionary
+ {
+ private struct ValueBuilder : IDictionary, IReadOnlyDictionary, IDictionary
+ {
+ ///
+ /// The immutable collection this builder is based on.
+ ///
+ private ImmutableSegmentedDictionary _dictionary;
+
+ ///
+ /// The current mutable collection this builder is operating on. This field is initialized to a copy of
+ /// the first time a change is made.
+ ///
+ private SegmentedDictionary? _mutableDictionary;
+
+ internal ValueBuilder(ImmutableSegmentedDictionary dictionary)
+ {
+ _dictionary = dictionary;
+ _mutableDictionary = null;
+ }
+
+ public IEqualityComparer KeyComparer
+ {
+ readonly get
+ {
+ return ReadOnlyDictionary.Comparer;
+ }
+
+ set
+ {
+ if (value is null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (value != KeyComparer)
+ {
+ // Rewrite the mutable dictionary using a new comparer
+ var valuesToAdd = ReadOnlyDictionary;
+ _mutableDictionary = new SegmentedDictionary(value);
+ _dictionary = default;
+ AddRange(valuesToAdd);
+ }
+ }
+ }
+
+ public readonly int Count => ReadOnlyDictionary.Count;
+
+ internal readonly SegmentedDictionary ReadOnlyDictionary => _mutableDictionary ?? _dictionary._dictionary;
+
+ readonly IEnumerable IReadOnlyDictionary.Keys => throw new NotSupportedException();
+
+ readonly IEnumerable IReadOnlyDictionary.Values => throw new NotSupportedException();
+
+ readonly ICollection IDictionary.Keys => throw new NotSupportedException();
+
+ readonly ICollection IDictionary.Values => throw new NotSupportedException();
+
+ readonly bool ICollection>.IsReadOnly => false;
+
+ readonly ICollection IDictionary.Keys => throw new NotSupportedException();
+
+ readonly ICollection IDictionary.Values => throw new NotSupportedException();
+
+ readonly bool IDictionary.IsReadOnly => false;
+
+ readonly bool IDictionary.IsFixedSize => false;
+
+ readonly object ICollection.SyncRoot => throw new NotSupportedException();
+
+ readonly bool ICollection.IsSynchronized => false;
+
+ public TValue this[TKey key]
+ {
+ readonly get => ReadOnlyDictionary[key];
+ set => GetOrCreateMutableDictionary()[key] = value;
+ }
+
+ object? IDictionary.this[object key]
+ {
+ readonly get => ((IDictionary)ReadOnlyDictionary)[key];
+ set => ((IDictionary)GetOrCreateMutableDictionary())[key] = value;
+ }
+
+ internal SegmentedDictionary GetOrCreateMutableDictionary()
+ {
+ if (_mutableDictionary is null)
+ {
+ var originalDictionary = RoslynImmutableInterlocked.InterlockedExchange(ref _dictionary, default);
+ if (originalDictionary.IsDefault)
+ throw new InvalidOperationException($"Unexpected concurrent access to {GetType()}");
+
+ _mutableDictionary = new SegmentedDictionary(originalDictionary._dictionary, originalDictionary.KeyComparer);
+ }
+
+ return _mutableDictionary;
+ }
+
+ public void Add(TKey key, TValue value)
+ {
+ if (Contains(new KeyValuePair(key, value)))
+ return;
+
+ GetOrCreateMutableDictionary().Add(key, value);
+ }
+
+ public void Add(KeyValuePair item)
+ => Add(item.Key, item.Value);
+
+ public void AddRange(IEnumerable> items)
+ {
+ if (items == null)
+ throw new ArgumentNullException(nameof(items));
+
+ foreach (var pair in items)
+ Add(pair.Key, pair.Value);
+ }
+
+ public void Clear()
+ {
+ if (ReadOnlyDictionary.Count != 0)
+ {
+ if (_mutableDictionary is null)
+ {
+ _mutableDictionary = new SegmentedDictionary(KeyComparer);
+ _dictionary = default;
+ }
+ else
+ {
+ _mutableDictionary.Clear();
+ }
+ }
+ }
+
+ public readonly bool Contains(KeyValuePair item)
+ {
+ return TryGetValue(item.Key, out var value)
+ && EqualityComparer.Default.Equals(value, item.Value);
+ }
+
+ public readonly bool ContainsKey(TKey key)
+ => ReadOnlyDictionary.ContainsKey(key);
+
+ public readonly bool ContainsValue(TValue value)
+ => ReadOnlyDictionary.ContainsValue(value);
+
+ public Enumerator GetEnumerator()
+ => new(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair);
+
+ public readonly TValue? GetValueOrDefault(TKey key)
+ {
+ if (TryGetValue(key, out var value))
+ return value;
+
+ return default;
+ }
+
+ public readonly TValue GetValueOrDefault(TKey key, TValue defaultValue)
+ {
+ if (TryGetValue(key, out var value))
+ return value;
+
+ return defaultValue;
+ }
+
+ public bool Remove(TKey key)
+ {
+ if (_mutableDictionary is null && !ContainsKey(key))
+ return false;
+
+ return GetOrCreateMutableDictionary().Remove(key);
+ }
+
+ public bool Remove(KeyValuePair item)
+ {
+ if (!Contains(item))
+ {
+ return false;
+ }
+
+ GetOrCreateMutableDictionary().Remove(item.Key);
+ return true;
+ }
+
+ public void RemoveRange(IEnumerable keys)
+ {
+ if (keys is null)
+ throw new ArgumentNullException(nameof(keys));
+
+ foreach (var key in keys)
+ {
+ Remove(key);
+ }
+ }
+
+#pragma warning disable IDE0251 // Make member 'readonly' (false positive: https://github.com/dotnet/roslyn/issues/72335)
+ public bool TryGetKey(TKey equalKey, out TKey actualKey)
+#pragma warning restore IDE0251 // Make member 'readonly'
+ {
+ foreach (var pair in this)
+ {
+ if (KeyComparer.Equals(pair.Key, equalKey))
+ {
+ actualKey = pair.Key;
+ return true;
+ }
+ }
+
+ actualKey = equalKey;
+ return false;
+ }
+
+#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
+ public readonly bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
+#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
+ => ReadOnlyDictionary.TryGetValue(key, out value);
+
+ public ImmutableSegmentedDictionary ToImmutable()
+ {
+ _dictionary = new ImmutableSegmentedDictionary(ReadOnlyDictionary);
+ _mutableDictionary = null;
+ return _dictionary;
+ }
+
+ readonly void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
+ => ((ICollection>)ReadOnlyDictionary).CopyTo(array, arrayIndex);
+
+ IEnumerator> IEnumerable>.GetEnumerator()
+ => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair);
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair);
+
+ readonly bool IDictionary.Contains(object key)
+ => ((IDictionary)ReadOnlyDictionary).Contains(key);
+
+ void IDictionary.Add(object key, object? value)
+ => ((IDictionary)GetOrCreateMutableDictionary()).Add(key, value);
+
+ IDictionaryEnumerator IDictionary.GetEnumerator()
+ => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.DictionaryEntry);
+
+ void IDictionary.Remove(object key)
+ => ((IDictionary)GetOrCreateMutableDictionary()).Remove(key);
+
+ readonly void ICollection.CopyTo(Array array, int index)
+ => ((ICollection)ReadOnlyDictionary).CopyTo(array, index);
+ }
+ }
+}
diff --git a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2.cs b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2.cs
index 99d8a6ce511d7..bdcdcdddcf96a 100644
--- a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2.cs
+++ b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2.cs
@@ -147,9 +147,9 @@ public ImmutableSegmentedDictionary Add(TKey key, TValue value)
if (self.Contains(new KeyValuePair(key, value)))
return self;
- var dictionary = new SegmentedDictionary(self._dictionary, self._dictionary.Comparer);
- dictionary.Add(key, value);
- return new ImmutableSegmentedDictionary(dictionary);
+ var builder = ToValueBuilder();
+ builder.Add(key, value);
+ return builder.ToImmutable();
}
public ImmutableSegmentedDictionary AddRange(IEnumerable> pairs)
@@ -162,21 +162,9 @@ public ImmutableSegmentedDictionary AddRange(IEnumerable? dictionary = null;
- foreach (var pair in pairs)
- {
- ICollection> collectionToCheck = dictionary ?? self._dictionary;
- if (collectionToCheck.Contains(pair))
- continue;
-
- dictionary ??= new SegmentedDictionary(self._dictionary, self._dictionary.Comparer);
- dictionary.Add(pair.Key, pair.Value);
- }
-
- if (dictionary is null)
- return self;
-
- return new ImmutableSegmentedDictionary(dictionary);
+ var builder = ToValueBuilder();
+ builder.AddRange(pairs);
+ return builder.ToImmutable();
}
public ImmutableSegmentedDictionary Clear()
@@ -211,9 +199,9 @@ public ImmutableSegmentedDictionary Remove(TKey key)
if (!self._dictionary.ContainsKey(key))
return self;
- var dictionary = new SegmentedDictionary(self._dictionary, self._dictionary.Comparer);
- dictionary.Remove(key);
- return new ImmutableSegmentedDictionary(dictionary);
+ var builder = ToValueBuilder();
+ builder.Remove(key);
+ return builder.ToImmutable();
}
public ImmutableSegmentedDictionary RemoveRange(IEnumerable keys)
@@ -221,7 +209,7 @@ public ImmutableSegmentedDictionary RemoveRange(IEnumerable
if (keys is null)
throw new ArgumentNullException(nameof(keys));
- var result = ToBuilder();
+ var result = ToValueBuilder();
result.RemoveRange(keys);
return result.ToImmutable();
}
@@ -234,9 +222,9 @@ public ImmutableSegmentedDictionary SetItem(TKey key, TValue value
return self;
}
- var dictionary = new SegmentedDictionary(self._dictionary, self._dictionary.Comparer);
- dictionary[key] = value;
- return new ImmutableSegmentedDictionary(dictionary);
+ var builder = ToValueBuilder();
+ builder[key] = value;
+ return builder.ToImmutable();
}
public ImmutableSegmentedDictionary SetItems(IEnumerable> items)
@@ -244,7 +232,7 @@ public ImmutableSegmentedDictionary SetItems(IEnumerable WithComparer(IEqualityComparer
public Builder ToBuilder()
=> new(this);
+ private ValueBuilder ToValueBuilder()
+ => new(this);
+
public override int GetHashCode()
=> _dictionary?.GetHashCode() ?? 0;
diff --git a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+Builder.cs b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+Builder.cs
index 646c2a4bb93ca..f5140e38238f5 100644
--- a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+Builder.cs
+++ b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+Builder.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -15,263 +14,115 @@ internal readonly partial struct ImmutableSegmentedHashSet
public sealed class Builder : ISet, IReadOnlyCollection
{
///
- /// The immutable collection this builder is based on.
+ /// The private builder implementation.
///
- private ImmutableSegmentedHashSet _set;
-
- ///
- /// The current mutable collection this builder is operating on. This field is initialized to a copy of
- /// the first time a change is made.
- ///
- private SegmentedHashSet? _mutableSet;
+ private ValueBuilder _builder;
internal Builder(ImmutableSegmentedHashSet set)
- {
- _set = set;
- _mutableSet = null;
- }
+ => _builder = new ValueBuilder(set);
///
public IEqualityComparer KeyComparer
{
- get
- {
- return ReadOnlySet.Comparer;
- }
-
- set
- {
- if (Equals(KeyComparer, value ?? EqualityComparer.Default))
- return;
-
- _mutableSet = new SegmentedHashSet(ReadOnlySet, value ?? EqualityComparer.Default);
- _set = default;
- }
+ get => _builder.KeyComparer;
+ set => _builder.KeyComparer = value;
}
///
- public int Count => ReadOnlySet.Count;
-
- private SegmentedHashSet ReadOnlySet => _mutableSet ?? _set._set;
-
- bool ICollection.IsReadOnly => false;
-
- private SegmentedHashSet GetOrCreateMutableSet()
- {
- if (_mutableSet is null)
- {
- var originalSet = RoslynImmutableInterlocked.InterlockedExchange(ref _set, default);
- if (originalSet.IsDefault)
- throw new InvalidOperationException($"Unexpected concurrent access to {GetType()}");
-
- _mutableSet = new SegmentedHashSet(originalSet._set, originalSet.KeyComparer);
- }
+ public int Count => _builder.Count;
- return _mutableSet;
- }
+ bool ICollection.IsReadOnly => ICollectionCalls.IsReadOnly(ref _builder);
///
public bool Add(T item)
- {
- if (_mutableSet is null && Contains(item))
- return false;
-
- return GetOrCreateMutableSet().Add(item);
- }
+ => _builder.Add(item);
///
public void Clear()
- {
- if (ReadOnlySet.Count != 0)
- {
- if (_mutableSet is null)
- {
- _mutableSet = new SegmentedHashSet(KeyComparer);
- _set = default;
- }
- else
- {
- _mutableSet.Clear();
- }
- }
- }
+ => _builder.Clear();
///
public bool Contains(T item)
- => ReadOnlySet.Contains(item);
+ => _builder.Contains(item);
///
public void ExceptWith(IEnumerable other)
{
- if (other is null)
- ThrowHelper.ThrowArgumentNullException(ExceptionArgument.other);
-
- if (_mutableSet is not null)
- {
- _mutableSet.ExceptWith(other);
- return;
- }
-
if (other == this)
{
- Clear();
- return;
- }
- else if (other is ImmutableSegmentedHashSet otherSet)
- {
- if (otherSet == _set)
- {
- Clear();
- return;
- }
- else if (otherSet.IsEmpty)
- {
- // No action required
- return;
- }
- else
- {
- GetOrCreateMutableSet().ExceptWith(otherSet._set);
- return;
- }
+ // The ValueBuilder knows how to optimize for this case by calling Clear, provided it does not need
+ // to access properties of the wrapping Builder instance.
+ _builder.ExceptWith(_builder.ReadOnlySet);
}
else
{
- // Manually enumerate to avoid changes to the builder if 'other' is empty or does not contain any
- // items present in the current set.
- SegmentedHashSet? mutableSet = null;
- foreach (var item in other)
- {
- if (mutableSet is null)
- {
- if (!ReadOnlySet.Contains(item))
- continue;
-
- mutableSet = GetOrCreateMutableSet();
- }
-
- mutableSet.Remove(item);
- }
-
- return;
+ _builder.ExceptWith(other);
}
}
///
public Enumerator GetEnumerator()
- => new Enumerator(GetOrCreateMutableSet());
+ => _builder.GetEnumerator();
///
public void IntersectWith(IEnumerable other)
- => GetOrCreateMutableSet().IntersectWith(other);
+ => _builder.IntersectWith(other);
///
public bool IsProperSubsetOf(IEnumerable other)
- => ReadOnlySet.IsProperSubsetOf(other);
+ => _builder.IsProperSubsetOf(other);
///
public bool IsProperSupersetOf(IEnumerable other)
- => ReadOnlySet.IsProperSupersetOf(other);
+ => _builder.IsProperSupersetOf(other);
///
public bool IsSubsetOf(IEnumerable other)
- => ReadOnlySet.IsSubsetOf(other);
+ => _builder.IsSubsetOf(other);
///
public bool IsSupersetOf(IEnumerable other)
- => ReadOnlySet.IsSupersetOf(other);
+ => _builder.IsSupersetOf(other);
///
public bool Overlaps(IEnumerable other)
- => ReadOnlySet.Overlaps(other);
+ => _builder.Overlaps(other);
///
public bool Remove(T item)
- {
- if (_mutableSet is null && !Contains(item))
- return false;
-
- return GetOrCreateMutableSet().Remove(item);
- }
+ => _builder.Remove(item);
///
public bool SetEquals(IEnumerable other)
- => ReadOnlySet.SetEquals(other);
+ => _builder.SetEquals(other);
///
public void SymmetricExceptWith(IEnumerable other)
- => GetOrCreateMutableSet().SymmetricExceptWith(other);
+ => _builder.SymmetricExceptWith(other);
///
public bool TryGetValue(T equalValue, out T actualValue)
- {
- if (ReadOnlySet.TryGetValue(equalValue, out var value))
- {
- actualValue = value;
- return true;
- }
-
- actualValue = equalValue;
- return false;
- }
+ => _builder.TryGetValue(equalValue, out actualValue);
///
public void UnionWith(IEnumerable other)
- {
- if (other is null)
- ThrowHelper.ThrowArgumentNullException(ExceptionArgument.other);
-
- if (_mutableSet is not null)
- {
- _mutableSet.UnionWith(other);
- return;
- }
-
- if (other is ImmutableSegmentedHashSet { IsEmpty: true })
- {
- return;
- }
- else
- {
- // Manually enumerate to avoid changes to the builder if 'other' is empty or only contains items
- // already present in the current set.
- SegmentedHashSet? mutableSet = null;
- foreach (var item in other)
- {
- if (mutableSet is null)
- {
- if (ReadOnlySet.Contains(item))
- continue;
-
- mutableSet = GetOrCreateMutableSet();
- }
-
- mutableSet.Add(item);
- }
-
- return;
- }
- }
+ => _builder.UnionWith(other);
///
public ImmutableSegmentedHashSet ToImmutable()
- {
- _set = new ImmutableSegmentedHashSet(ReadOnlySet);
- _mutableSet = null;
- return _set;
- }
+ => _builder.ToImmutable();
void ICollection.Add(T item)
- => ((ICollection)GetOrCreateMutableSet()).Add(item);
+ => ICollectionCalls.Add(ref _builder, item);
void ICollection.CopyTo(T[] array, int arrayIndex)
- => ((ICollection)ReadOnlySet).CopyTo(array, arrayIndex);
+ => ICollectionCalls.CopyTo(ref _builder, array, arrayIndex);
IEnumerator IEnumerable.GetEnumerator()
- => GetEnumerator();
+ => IEnumerableCalls.GetEnumerator(ref _builder);
IEnumerator IEnumerable.GetEnumerator()
- => GetEnumerator();
+ => IEnumerableCalls.GetEnumerator(ref _builder);
}
}
}
diff --git a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+ValueBuilder.cs b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+ValueBuilder.cs
new file mode 100644
index 0000000000000..c672df7acd97f
--- /dev/null
+++ b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+ValueBuilder.cs
@@ -0,0 +1,282 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using Microsoft.CodeAnalysis.Collections.Internal;
+
+namespace Microsoft.CodeAnalysis.Collections
+{
+ internal readonly partial struct ImmutableSegmentedHashSet
+ {
+ private struct ValueBuilder : ISet, IReadOnlyCollection
+ {
+ ///
+ /// The immutable collection this builder is based on.
+ ///
+ private ImmutableSegmentedHashSet _set;
+
+ ///
+ /// The current mutable collection this builder is operating on. This field is initialized to a copy of
+ /// the first time a change is made.
+ ///
+ private SegmentedHashSet? _mutableSet;
+
+ internal ValueBuilder(ImmutableSegmentedHashSet set)
+ {
+ _set = set;
+ _mutableSet = null;
+ }
+
+ ///
+ public IEqualityComparer KeyComparer
+ {
+ readonly get
+ {
+ return ReadOnlySet.Comparer;
+ }
+
+ set
+ {
+ if (Equals(KeyComparer, value ?? EqualityComparer.Default))
+ return;
+
+ _mutableSet = new SegmentedHashSet(ReadOnlySet, value ?? EqualityComparer.Default);
+ _set = default;
+ }
+ }
+
+ ///
+ public readonly int Count => ReadOnlySet.Count;
+
+ internal readonly SegmentedHashSet ReadOnlySet => _mutableSet ?? _set._set;
+
+ readonly bool ICollection.IsReadOnly => false;
+
+ private SegmentedHashSet GetOrCreateMutableSet()
+ {
+ if (_mutableSet is null)
+ {
+ var originalSet = RoslynImmutableInterlocked.InterlockedExchange(ref _set, default);
+ if (originalSet.IsDefault)
+ throw new InvalidOperationException($"Unexpected concurrent access to {GetType()}");
+
+ _mutableSet = new SegmentedHashSet(originalSet._set, originalSet.KeyComparer);
+ }
+
+ return _mutableSet;
+ }
+
+ ///
+ public bool Add(T item)
+ {
+ if (_mutableSet is null && Contains(item))
+ return false;
+
+ return GetOrCreateMutableSet().Add(item);
+ }
+
+ ///
+ public void Clear()
+ {
+ if (ReadOnlySet.Count != 0)
+ {
+ if (_mutableSet is null)
+ {
+ _mutableSet = new SegmentedHashSet(KeyComparer);
+ _set = default;
+ }
+ else
+ {
+ _mutableSet.Clear();
+ }
+ }
+ }
+
+ ///
+ public readonly bool Contains(T item)
+ => ReadOnlySet.Contains(item);
+
+ ///
+ public void ExceptWith(IEnumerable other)
+ {
+ if (other is null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.other);
+
+ if (_mutableSet is not null)
+ {
+ _mutableSet.ExceptWith(other);
+ return;
+ }
+
+ // ValueBuilder is not a public API, so there shouldn't be any callers trying to pass a boxed instance
+ // to this method.
+ Debug.Assert(other is not ValueBuilder);
+
+ if (other == ReadOnlySet)
+ {
+ Clear();
+ return;
+ }
+ else if (other is ImmutableSegmentedHashSet otherSet)
+ {
+ if (otherSet == _set)
+ {
+ Clear();
+ return;
+ }
+ else if (otherSet.IsEmpty)
+ {
+ // No action required
+ return;
+ }
+ else
+ {
+ GetOrCreateMutableSet().ExceptWith(otherSet._set);
+ return;
+ }
+ }
+ else
+ {
+ // Manually enumerate to avoid changes to the builder if 'other' is empty or does not contain any
+ // items present in the current set.
+ SegmentedHashSet? mutableSet = null;
+ foreach (var item in other)
+ {
+ if (mutableSet is null)
+ {
+ if (!ReadOnlySet.Contains(item))
+ continue;
+
+ mutableSet = GetOrCreateMutableSet();
+ }
+
+ mutableSet.Remove(item);
+ }
+
+ return;
+ }
+ }
+
+ ///
+ public Enumerator GetEnumerator()
+ => new(GetOrCreateMutableSet());
+
+ ///
+ public void IntersectWith(IEnumerable other)
+ => GetOrCreateMutableSet().IntersectWith(other);
+
+ ///
+ public readonly bool IsProperSubsetOf(IEnumerable other)
+ => ReadOnlySet.IsProperSubsetOf(other);
+
+ ///
+ public readonly bool IsProperSupersetOf(IEnumerable other)
+ => ReadOnlySet.IsProperSupersetOf(other);
+
+ ///
+ public readonly bool IsSubsetOf(IEnumerable other)
+ => ReadOnlySet.IsSubsetOf(other);
+
+ ///
+ public readonly bool IsSupersetOf(IEnumerable other)
+ => ReadOnlySet.IsSupersetOf(other);
+
+ ///
+ public readonly bool Overlaps(IEnumerable other)
+ => ReadOnlySet.Overlaps(other);
+
+ ///
+ public bool Remove(T item)
+ {
+ if (_mutableSet is null && !Contains(item))
+ return false;
+
+ return GetOrCreateMutableSet().Remove(item);
+ }
+
+ ///
+ public readonly bool SetEquals(IEnumerable other)
+ => ReadOnlySet.SetEquals(other);
+
+ ///
+ public void SymmetricExceptWith(IEnumerable other)
+ => GetOrCreateMutableSet().SymmetricExceptWith(other);
+
+ ///
+ public readonly bool TryGetValue(T equalValue, out T actualValue)
+ {
+ if (ReadOnlySet.TryGetValue(equalValue, out var value))
+ {
+ actualValue = value;
+ return true;
+ }
+
+ actualValue = equalValue;
+ return false;
+ }
+
+ ///
+ public void UnionWith(IEnumerable other)
+ {
+ if (other is null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.other);
+
+ if (_mutableSet is not null)
+ {
+ _mutableSet.UnionWith(other);
+ return;
+ }
+
+ if (other is ImmutableSegmentedHashSet { IsEmpty: true })
+ {
+ return;
+ }
+ else
+ {
+ // Manually enumerate to avoid changes to the builder if 'other' is empty or only contains items
+ // already present in the current set.
+ SegmentedHashSet? mutableSet = null;
+ foreach (var item in other)
+ {
+ if (mutableSet is null)
+ {
+ if (ReadOnlySet.Contains(item))
+ continue;
+
+ mutableSet = GetOrCreateMutableSet();
+ }
+
+ mutableSet.Add(item);
+ }
+
+ return;
+ }
+ }
+
+ ///
+ public ImmutableSegmentedHashSet ToImmutable()
+ {
+ _set = new ImmutableSegmentedHashSet(ReadOnlySet);
+ _mutableSet = null;
+ return _set;
+ }
+
+ void ICollection.Add(T item)
+ => ((ICollection)GetOrCreateMutableSet()).Add(item);
+
+ readonly void ICollection.CopyTo(T[] array, int arrayIndex)
+ => ((ICollection)ReadOnlySet).CopyTo(array, arrayIndex);
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+ }
+ }
+}
diff --git a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs
index a00875ab0a459..b99e4e99373cc 100644
--- a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs
+++ b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs
@@ -10,6 +10,57 @@
namespace Microsoft.CodeAnalysis.Collections
{
+ ///
+ /// Represents a segmented hash set that is immutable; meaning it cannot be changed once it is created.
+ ///
+ ///
+ /// There are different scenarios best for and others
+ /// best for .
+ ///
+ /// The following table summarizes the performance characteristics of
+ /// :
+ ///
+ ///
+ /// -
+ /// Operation
+ /// Complexity
+ /// Complexity
+ /// Comments
+ ///
+ /// -
+ /// Contains
+ /// O(1)
+ /// O(log n)
+ /// Directly index into the underlying segmented list
+ ///
+ /// -
+ /// Add()
+ /// O(n)
+ /// O(log n)
+ /// Requires creating a new segmented hash set and cloning all impacted segments
+ ///
+ ///
+ ///
+ /// This type is backed by segmented arrays to avoid using the Large Object Heap without impacting algorithmic
+ /// complexity.
+ ///
+ /// The type of the value in the set.
+ ///
+ /// This type has a documented contract of being exactly one reference-type field in size. Our own
+ /// class depends on it, as well as others externally.
+ ///
+ /// IMPORTANT NOTICE FOR MAINTAINERS AND REVIEWERS:
+ ///
+ /// This type should be thread-safe. As a struct, it cannot protect its own fields from being changed from one
+ /// thread while its members are executing on other threads because structs can change in place simply by
+ /// reassigning the field containing this struct. Therefore it is extremely important that ⚠⚠ Every member
+ /// should only dereference this ONCE ⚠⚠. If a member needs to reference the
+ /// field, that counts as a dereference of this. Calling other instance members
+ /// (properties or methods) also counts as dereferencing this. Any member that needs to use this more
+ /// than once must instead assign this to a local variable and use that for the rest of the code instead.
+ /// This effectively copies the one field in the struct to a local variable so that it is insulated from other
+ /// threads.
+ ///
internal readonly partial struct ImmutableSegmentedHashSet : IImmutableSet, ISet, ICollection, IEquatable>
{
///
@@ -67,9 +118,8 @@ public ImmutableSegmentedHashSet Add(T value)
}
else
{
- // TODO: Avoid the builder allocation
// TODO: Reuse all pages with no changes
- var builder = self.ToBuilder();
+ var builder = self.ToValueBuilder();
builder.Add(value);
return builder.ToImmutable();
}
@@ -113,9 +163,8 @@ public ImmutableSegmentedHashSet Except(IEnumerable other)
}
else
{
- // TODO: Avoid the builder allocation
// TODO: Reuse all pages with no changes
- var builder = self.ToBuilder();
+ var builder = self.ToValueBuilder();
builder.ExceptWith(other);
return builder.ToImmutable();
}
@@ -136,9 +185,8 @@ public ImmutableSegmentedHashSet Intersect(IEnumerable other)
}
else
{
- // TODO: Avoid the builder allocation
// TODO: Reuse all pages with no changes
- var builder = self.ToBuilder();
+ var builder = self.ToValueBuilder();
builder.IntersectWith(other);
return builder.ToImmutable();
}
@@ -175,9 +223,8 @@ public ImmutableSegmentedHashSet Remove(T value)
}
else
{
- // TODO: Avoid the builder allocation
// TODO: Reuse all pages with no changes
- var builder = self.ToBuilder();
+ var builder = self.ToValueBuilder();
builder.Remove(value);
return builder.ToImmutable();
}
@@ -206,9 +253,8 @@ public ImmutableSegmentedHashSet SymmetricExcept(IEnumerable other)
}
else
{
- // TODO: Avoid the builder allocation
// TODO: Reuse all pages with no changes
- var builder = self.ToBuilder();
+ var builder = self.ToValueBuilder();
builder.SymmetricExceptWith(other);
return builder.ToImmutable();
}
@@ -240,9 +286,8 @@ public ImmutableSegmentedHashSet Union(IEnumerable other)
return otherSet.WithComparer(self.KeyComparer);
}
- // TODO: Avoid the builder allocation
// TODO: Reuse all pages with no changes
- var builder = self.ToBuilder();
+ var builder = self.ToValueBuilder();
builder.UnionWith(other);
return builder.ToImmutable();
}
@@ -251,6 +296,9 @@ public ImmutableSegmentedHashSet Union(IEnumerable other)
public Builder ToBuilder()
=> new(this);
+ private ValueBuilder ToValueBuilder()
+ => new ValueBuilder(this);
+
///
public ImmutableSegmentedHashSet WithComparer(IEqualityComparer? equalityComparer)
{
diff --git a/src/Dependencies/Collections/ImmutableSegmentedList`1+Builder.cs b/src/Dependencies/Collections/ImmutableSegmentedList`1+Builder.cs
index 865107bbe052e..174d53ff8904b 100644
--- a/src/Dependencies/Collections/ImmutableSegmentedList`1+Builder.cs
+++ b/src/Dependencies/Collections/ImmutableSegmentedList`1+Builder.cs
@@ -14,7 +14,7 @@ internal partial struct ImmutableSegmentedList
public sealed class Builder : IList, IReadOnlyList, IList
{
///
- /// The immutable collection this builder is based on.
+ /// The private builder implementation.
///
private ValueBuilder _builder;
diff --git a/src/Dependencies/Collections/Internal/ICollectionCalls`1.cs b/src/Dependencies/Collections/Internal/ICollectionCalls`1.cs
index 3eec844a90f5b..45fa0142859da 100644
--- a/src/Dependencies/Collections/Internal/ICollectionCalls`1.cs
+++ b/src/Dependencies/Collections/Internal/ICollectionCalls`1.cs
@@ -20,5 +20,13 @@ internal static class ICollectionCalls
public static bool IsReadOnly(ref TCollection collection)
where TCollection : ICollection
=> collection.IsReadOnly;
+
+ public static void Add(ref TCollection collection, T item)
+ where TCollection : ICollection
+ => collection.Add(item);
+
+ public static void CopyTo(ref TCollection collection, T[] array, int arrayIndex)
+ where TCollection : ICollection
+ => collection.CopyTo(array, arrayIndex);
}
}
diff --git a/src/Dependencies/Collections/Internal/IDictionaryCalls.cs b/src/Dependencies/Collections/Internal/IDictionaryCalls.cs
new file mode 100644
index 0000000000000..79d95fd22ef6e
--- /dev/null
+++ b/src/Dependencies/Collections/Internal/IDictionaryCalls.cs
@@ -0,0 +1,57 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+
+namespace Microsoft.CodeAnalysis.Collections.Internal
+{
+ ///
+ /// Provides static methods to invoke members on value types that explicitly implement the
+ /// member.
+ ///
+ ///
+ /// Normally, invocation of explicit interface members requires boxing or copying the value type, which is
+ /// especially problematic for operations that mutate the value. Invocation through these helpers behaves like a
+ /// normal call to an implicitly implemented member.
+ ///
+ internal static class IDictionaryCalls
+ {
+ public static bool IsFixedSize(ref TDictionary dictionary)
+ where TDictionary : IDictionary
+ => dictionary.IsFixedSize;
+
+ public static bool IsReadOnly(ref TDictionary dictionary)
+ where TDictionary : IDictionary
+ => dictionary.IsReadOnly;
+
+ public static object? GetItem(ref TDictionary dictionary, object key)
+ where TDictionary : IDictionary
+ => dictionary[key];
+
+ public static void SetItem(ref TDictionary dictionary, object key, object? value)
+ where TDictionary : IDictionary
+ => dictionary[key] = value;
+
+ public static void Add(ref TDictionary dictionary, object key, object? value)
+ where TDictionary : IDictionary
+ => dictionary.Add(key, value);
+
+ public static bool Contains(ref TDictionary dictionary, object key)
+ where TDictionary : IDictionary
+ => dictionary.Contains(key);
+
+ public static void CopyTo(ref TDictionary dictionary, Array array, int index)
+ where TDictionary : IDictionary
+ => dictionary.CopyTo(array, index);
+
+ public static IDictionaryEnumerator GetEnumerator(ref TDictionary dictionary)
+ where TDictionary : IDictionary
+ => dictionary.GetEnumerator();
+
+ public static void Remove(ref TDictionary dictionary, object key)
+ where TDictionary : IDictionary
+ => dictionary.Remove(key);
+ }
+}
diff --git a/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems b/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems
index 793bff9426dc8..e17523e5a7ffd 100644
--- a/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems
+++ b/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems
@@ -19,6 +19,7 @@
+
@@ -26,6 +27,7 @@
+
@@ -40,6 +42,7 @@
+