| File: System\Collections\ObjectModel\KeyedCollection.cs | Web Access |
| Project: src\runtime\src\libraries\System.ObjectModel\src\System.ObjectModel.csproj (System.ObjectModel) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace System.Collections.ObjectModel { [Serializable] [DebuggerTypeProxy(typeof(KeyedCollection<,>.KeyedCollectionDebugView))] [DebuggerDisplay("Count = {Count}")] [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] public abstract class KeyedCollection<TKey, TItem> : Collection<TItem> where TKey : notnull { private const int DefaultThreshold = 0; private readonly IEqualityComparer<TKey> comparer; // Do not rename (binary serialization) private Dictionary<TKey, TItem>? dict; // Do not rename (binary serialization) private int keyCount; // Do not rename (binary serialization) private readonly int threshold; // Do not rename (binary serialization) protected KeyedCollection() : this(null, DefaultThreshold) { } protected KeyedCollection(IEqualityComparer<TKey>? comparer) : this(comparer, DefaultThreshold) { } protected KeyedCollection(IEqualityComparer<TKey>? comparer, int dictionaryCreationThreshold) : base(new List<TItem>()) // Be explicit about the use of List<T> so we can foreach over // Items internally without enumerator allocations. { if (dictionaryCreationThreshold < -1) { throw new ArgumentOutOfRangeException(nameof(dictionaryCreationThreshold), SR.ArgumentOutOfRange_InvalidThreshold); } this.comparer = comparer ?? EqualityComparer<TKey>.Default; threshold = dictionaryCreationThreshold == -1 ? int.MaxValue : dictionaryCreationThreshold; } /// <summary> /// Enables the use of foreach internally without allocations using <see cref="List{T}"/>'s struct enumerator. /// </summary> private new List<TItem> Items { get { Debug.Assert(base.Items is List<TItem>); return (List<TItem>)base.Items; } } public IEqualityComparer<TKey> Comparer => comparer; public TItem this[TKey key] { get { TItem item; if (TryGetValue(key, out item!)) { return item; } throw new KeyNotFoundException(SR.Format(SR.Arg_KeyNotFoundWithKey, key.ToString())); } } public bool Contains(TKey key) { ArgumentNullException.ThrowIfNull(key); if (dict != null) { return dict.ContainsKey(key); } foreach (TItem item in Items) { if (comparer.Equals(GetKeyForItem(item), key)) { return true; } } return false; } public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TItem item) { ArgumentNullException.ThrowIfNull(key); if (dict != null) { return dict.TryGetValue(key, out item!); } foreach (TItem itemInItems in Items) { TKey keyInItems = GetKeyForItem(itemInItems); if (keyInItems != null && comparer.Equals(key, keyInItems)) { item = itemInItems; return true; } } item = default; return false; } private bool ContainsItem(TItem item) { TKey key; if ((dict == null) || ((key = GetKeyForItem(item)) == null)) { return Items.Contains(item); } TItem itemInDict; if (dict.TryGetValue(key, out itemInDict!)) { return EqualityComparer<TItem>.Default.Equals(itemInDict, item); } return false; } public bool Remove(TKey key) { ArgumentNullException.ThrowIfNull(key); if (dict != null) { TItem item; return dict.TryGetValue(key, out item!) && Remove(item); } for (int i = 0; i < Items.Count; i++) { if (comparer.Equals(GetKeyForItem(Items[i]), key)) { RemoveItem(i); return true; } } return false; } protected IDictionary<TKey, TItem>? Dictionary => dict; protected void ChangeItemKey(TItem item, TKey newKey) { if (!ContainsItem(item)) { throw new ArgumentException(SR.Argument_ItemNotExist, nameof(item)); } TKey oldKey = GetKeyForItem(item); if (!comparer.Equals(oldKey, newKey)) { if (newKey != null) { AddKey(newKey, item); } if (oldKey != null) { RemoveKey(oldKey); } } } protected override void ClearItems() { base.ClearItems(); dict?.Clear(); keyCount = 0; } protected abstract TKey GetKeyForItem(TItem item); protected override void InsertItem(int index, TItem item) { TKey key = GetKeyForItem(item); if (key != null) { AddKey(key, item); } base.InsertItem(index, item); } protected override void RemoveItem(int index) { TKey key = GetKeyForItem(Items[index]); if (key != null) { RemoveKey(key); } base.RemoveItem(index); } protected override void SetItem(int index, TItem item) { TKey newKey = GetKeyForItem(item); TKey oldKey = GetKeyForItem(Items[index]); if (comparer.Equals(oldKey, newKey)) { if (newKey != null && dict != null) { dict[newKey] = item; } } else { if (newKey != null) { AddKey(newKey, item); } if (oldKey != null) { RemoveKey(oldKey); } } base.SetItem(index, item); } private void AddKey(TKey key, TItem item) { if (dict != null) { dict.Add(key, item); } else if (keyCount == threshold) { CreateDictionary(); dict!.Add(key, item); } else { if (Contains(key)) { throw new ArgumentException(SR.Format(SR.Argument_AddingDuplicate, key), nameof(key)); } keyCount++; } } private void CreateDictionary() { dict = new Dictionary<TKey, TItem>(comparer); foreach (TItem item in Items) { TKey key = GetKeyForItem(item); if (key != null) { dict.Add(key, item); } } } private void RemoveKey(TKey key) { Debug.Assert(key != null, "key shouldn't be null!"); if (dict != null) { dict.Remove(key); } else { keyCount--; } } private sealed class KeyedCollectionDebugView { private readonly KeyedCollection<TKey, TItem> _collection; public KeyedCollectionDebugView(KeyedCollection<TKey, TItem> collection) { ArgumentNullException.ThrowIfNull(collection); _collection = collection; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public KeyedCollectionDebugViewItem[] Items { get { var items = new KeyedCollectionDebugViewItem[_collection.Count]; for (int i = 0; i < items.Length; i++) { TItem item = _collection[i]; items[i] = new KeyedCollectionDebugViewItem(_collection.GetKeyForItem(item), item); } return items; } } [DebuggerDisplay("{Value}", Name = "[{Key}]")] internal readonly struct KeyedCollectionDebugViewItem { public KeyedCollectionDebugViewItem(TKey key, TItem value) { Key = key; Value = value; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public TKey Key { get; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public TItem Value { get; } } } } }