File: Collections\ItemDictionarySlim.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// 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;
using System.Collections.Generic;
using Microsoft.Build.Execution;
 
namespace Microsoft.Build.Collections
{
    /// <summary>
    /// A simple implementation of ItemDictionary intended for populating Scope tables and merging adds/removes,
    /// without the overhead of locking and additional lookups.
    /// </summary>
    internal sealed class ItemDictionarySlim : IEnumerable<KeyValuePair<string, List<ProjectItemInstance>>>
    {
        private readonly Dictionary<string, List<ProjectItemInstance>> _itemLists;
 
        public ItemDictionarySlim() =>
            _itemLists = new Dictionary<string, List<ProjectItemInstance>>(MSBuildNameIgnoreCaseComparer.Default);
 
        /// <summary>
        /// Gets all items of the given type, or null if there are none.
        /// </summary>
        public List<ProjectItemInstance>? this[string itemType] =>
            _itemLists.TryGetValue(itemType, out List<ProjectItemInstance>? itemsOfType) ? itemsOfType : null;
 
        /// <summary>
        /// Returns true if there are any items of the given type.
        /// </summary>
        public bool ContainsKey(string itemType) => _itemLists.ContainsKey(itemType);
 
        /// <summary>
        /// Adds a single item to the matching list for its type.
        /// </summary>
        public void Add(ProjectItemInstance projectItem)
        {
            if (!_itemLists.TryGetValue(projectItem.ItemType, out List<ProjectItemInstance>? list))
            {
                list = [];
                _itemLists[projectItem.ItemType] = list;
            }
 
            list.Add(projectItem);
        }
 
        /// <summary>
        /// Imports and merges all items from an existing item dictionary, appending to any existing list.
        /// </summary>
        /// <remarks>
        /// This should only be called when merging between scopes, as it may take list references from the other dictionary.
        /// This is safe since we create these internal lists, and the owning Scope will always be discarded after a merge.
        /// </remarks>
        public void ImportItems(ItemDictionarySlim other)
        {
            foreach (KeyValuePair<string, List<ProjectItemInstance>> kvp in other._itemLists)
            {
                string itemType = kvp.Key;
                List<ProjectItemInstance> itemsToAdd = kvp.Value;
 
                if (_itemLists.TryGetValue(itemType, out List<ProjectItemInstance>? list))
                {
                    // There are already items of this type, so append to the existing list
                    list.AddRange(itemsToAdd);
                }
                else
                {
                    // Otherwise, steal the reference instead of copying items out.
                    _itemLists[itemType] = itemsToAdd;
                }
            }
        }
 
        /// <summary>
        /// Imports and merges all items of the given type, appending to any existing list.
        /// </summary>
        public void ImportItemsOfType(string itemType, IEnumerable<ProjectItemInstance> items)
        {
            if (!_itemLists.TryGetValue(itemType, out List<ProjectItemInstance>? list))
            {
                list = [];
                _itemLists[itemType] = list;
            }
 
            list.AddRange(items);
        }
 
        /// <summary>
        /// Sets the capacity for the item list matching the given type.
        /// </summary>
        /// <remarks>
        /// Useful for dealing with IEnumerable if we know the upper bound or estimate of items to be added.
        /// </remarks>
        internal void EnsureCapacityForItemType(string itemType, int capacity)
        {
            if (!_itemLists.TryGetValue(itemType, out List<ProjectItemInstance>? list))
            {
                list = new List<ProjectItemInstance>(capacity);
                _itemLists[itemType] = list;
            }
            else if (capacity > list.Capacity)
            {
                // Conditional since List.Capacity will throw if set less than its current value.
                list.Capacity = capacity;
            }
        }
 
        public Dictionary<string, List<ProjectItemInstance>>.Enumerator GetEnumerator() => _itemLists.GetEnumerator();
 
        IEnumerator<KeyValuePair<string, List<ProjectItemInstance>>> IEnumerable<KeyValuePair<string, List<ProjectItemInstance>>>.GetEnumerator() => GetEnumerator();
 
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }
}