|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;
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 static readonly ImmutableDictionary<string, V> NameComparerDictionaryPrototype = ImmutableDictionary.Create<string, V>(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 static readonly ImmutableDictionary<string, V> OrdinalIgnoreCaseComparerDictionaryPrototype = ImmutableDictionary.Create<string, V>(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 a specified comparer for the keys
/// </summary>
internal CopyOnWriteDictionary(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);
}
}
/// <summary>
/// IDictionary implementation
/// </summary>
object? IDictionary.this[object key]
{
get
{
TryGetValue((string)key, out V? val);
return val;
}
#nullable disable
set => this[(string)key] = (V)value;
#nullable enable
}
/// <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
}
#nullable disable
/// <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);
}
#nullable restore
/// <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();
}
#nullable disable
/// <summary>
/// IDictionary implementation.
/// </summary>
void IDictionary.Add(object key, object value)
{
Add((string)key, (V)value);
}
#nullable enable
/// <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);
}
}
}
|