File: System\ComponentModel\Composition\Hosting\ImportEngine.RecompositionManager.cs
Web Access
Project: src\src\libraries\System.ComponentModel.Composition\src\System.ComponentModel.Composition.csproj (System.ComponentModel.Composition)
// 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.ComponentModel.Composition.Primitives;
using System.Linq;
using Microsoft.Internal.Collections;
 
namespace System.ComponentModel.Composition.Hosting
{
    public partial class ImportEngine
    {
        /// <summary>
        ///     Used by the <see cref="ImportEngine"/> to effiecently store and retrieve the list of parts
        ///     that will be affected by changes to exports. This allows the <see cref="ImportEngine"/> to properly
        ///     block breaking changes and also recompose imports as appropriate.
        /// </summary>
        private sealed class RecompositionManager
        {
            private readonly WeakReferenceCollection<PartManager> _partsToIndex = new WeakReferenceCollection<PartManager>();
            private readonly WeakReferenceCollection<PartManager> _partsToUnindex = new WeakReferenceCollection<PartManager>();
            private readonly Dictionary<string, WeakReferenceCollection<PartManager>> _partManagerIndex = new Dictionary<string, WeakReferenceCollection<PartManager>>();
 
            public void AddPartToIndex(PartManager partManager)
            {
                _partsToIndex.Add(partManager);
            }
 
            public void AddPartToUnindex(PartManager partManager)
            {
                _partsToUnindex.Add(partManager);
            }
 
            public List<PartManager> GetAffectedParts(IEnumerable<string> changedContractNames)
            {
                UpdateImportIndex();
 
                List<PartManager> parts = new List<PartManager>();
 
                parts.AddRange(GetPartsImporting(ImportDefinition.EmptyContractName));
 
                foreach (string contractName in changedContractNames)
                {
                    parts.AddRange(GetPartsImporting(contractName));
                }
 
                return parts;
            }
 
            public static IEnumerable<ImportDefinition> GetAffectedImports(ComposablePart part, IEnumerable<ExportDefinition> changedExports)
            {
                return part.ImportDefinitions.Where(import => IsAffectedImport(import, changedExports));
            }
 
            private static bool IsAffectedImport(ImportDefinition import, IEnumerable<ExportDefinition> changedExports)
            {
                // This could be more efficient still if the export definitions were indexed by contract name,
                // only worth revisiting if we need to squeeze more performance out of recomposition
                foreach (var export in changedExports)
                {
                    if (import.IsConstraintSatisfiedBy(export))
                    {
                        return true;
                    }
                }
 
                return false;
            }
 
            public IEnumerable<PartManager> GetPartsImporting(string contractName)
            {
                if (!_partManagerIndex.TryGetValue(contractName, out WeakReferenceCollection<PartManager>? partManagerList))
                {
                    return Enumerable.Empty<PartManager>();
                }
 
                return partManagerList.AliveItemsToList();
            }
 
            private void AddIndexEntries(PartManager partManager)
            {
                foreach (string contractName in partManager.GetImportedContractNames())
                {
                    if (!_partManagerIndex.TryGetValue(contractName, out WeakReferenceCollection<PartManager>? indexEntries))
                    {
                        indexEntries = new WeakReferenceCollection<PartManager>();
                        _partManagerIndex.Add(contractName, indexEntries);
                    }
 
                    if (!indexEntries.Contains(partManager))
                    {
                        indexEntries.Add(partManager);
                    }
                }
            }
 
            private void RemoveIndexEntries(PartManager partManager)
            {
                foreach (string contractName in partManager.GetImportedContractNames())
                {
                    if (_partManagerIndex.TryGetValue(contractName, out WeakReferenceCollection<PartManager>? indexEntries))
                    {
                        indexEntries.Remove(partManager);
                        var aliveItems = indexEntries.AliveItemsToList();
 
                        if (aliveItems.Count == 0)
                        {
                            _partManagerIndex.Remove(contractName);
                        }
                    }
                }
            }
 
            private void UpdateImportIndex()
            {
                var partsToIndex = _partsToIndex.AliveItemsToList();
                _partsToIndex.Clear();
 
                List<PartManager?> partsToUnindex = _partsToUnindex.AliveItemsToList()!;
                _partsToUnindex.Clear();
 
                if (partsToIndex.Count == 0 && partsToUnindex.Count == 0)
                {
                    return;
                }
 
                foreach (var partManager in partsToIndex)
                {
                    var index = partsToUnindex.IndexOf(partManager);
 
                    // If the same part is being added and removed we can ignore both
                    if (index >= 0)
                    {
                        partsToUnindex[index] = null;
                    }
                    else
                    {
                        AddIndexEntries(partManager);
                    }
                }
 
                foreach (var partManager in partsToUnindex)
                {
                    if (partManager != null)
                    {
                        RemoveIndexEntries(partManager);
                    }
                }
            }
        }
    }
}