File: System\ComponentModel\Composition\Hosting\AggregateExportProvider.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.Collections.ObjectModel;
using System.ComponentModel.Composition.Primitives;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.Internal.Collections;
 
namespace System.ComponentModel.Composition.Hosting
{
    public class AggregateExportProvider : ExportProvider, IDisposable
    {
        private readonly ReadOnlyCollection<ExportProvider> _readOnlyProviders;
        private readonly ExportProvider[] _providers;
        private volatile int _isDisposed;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="AggregateExportProvider"/> class.
        /// </summary>
        /// <param name="providers">The prioritized list of export providers.</param>
        /// <exception cref="ArgumentException">
        ///     <paramref name="providers"/> contains an element that is <see langword="null"/>.
        /// </exception>
        /// <remarks>
        ///     <para>
        ///         The <see cref="AggregateExportProvider"/> will consult the providers in the order they have been specified when
        ///         executing <see cref="ExportProvider.GetExports(ImportDefinition,AtomicComposition)"/>.
        ///     </para>
        ///     <para>
        ///         The <see cref="AggregateExportProvider"/> does not take ownership of the specified providers.
        ///         That is, it will not try to dispose of any of them when it gets disposed.
        ///     </para>
        /// </remarks>
        public AggregateExportProvider(params ExportProvider[]? providers)
        {
            // NOTE : we optimize for the array case here, because the collection of providers is typically tiny
            // Arrays are much more compact to store and much faster to create and enumerate
            ExportProvider[]? copiedProviders = null;
            if (providers != null)
            {
                copiedProviders = new ExportProvider[providers.Length];
                for (int i = 0; i < providers.Length; i++)
                {
                    ExportProvider provider = providers[i];
                    if (provider == null)
                    {
                        throw ExceptionBuilder.CreateContainsNullElement(nameof(providers));
                    }
 
                    copiedProviders[i] = provider;
 
                    provider.ExportsChanged += OnExportChangedInternal;
                    provider.ExportsChanging += OnExportChangingInternal;
                }
            }
            else
            {
                copiedProviders = Array.Empty<ExportProvider>();
            }
 
            _providers = copiedProviders;
            _readOnlyProviders = Array.AsReadOnly(_providers);
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="AggregateExportProvider"/> class.
        /// </summary>
        /// <param name="providers">The prioritized list of export providers. The providers are consulted in order in which they are supplied.</param>
        /// <remarks>
        ///     <para>
        ///         The <see cref="AggregateExportProvider"/> will consult the providers in the order they have been specified when
        ///         executing <see cref="ExportProvider.GetExports(ImportDefinition,AtomicComposition)"/>.
        ///     </para>
        ///     <para>
        ///         The <see cref="AggregateExportProvider"/> does not take ownership of the specified providers.
        ///         That is, it will not try to dispose of any of them when it gets disposed.
        ///     </para>
        /// </remarks>
        public AggregateExportProvider(IEnumerable<ExportProvider>? providers)
            : this(providers?.AsArray())
        {
        }
 
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
                {
                    foreach (ExportProvider provider in _providers)
                    {
                        provider.ExportsChanged -= OnExportChangedInternal;
                        provider.ExportsChanging -= OnExportChangingInternal;
                    }
                }
            }
        }
 
        /// <summary>
        ///     Gets the export providers which the aggregate export provider aggregates.
        /// </summary>
        /// <value>
        ///     A <see cref="ReadOnlyCollection{T}"/> of <see cref="ExportProvider"/> objects
        ///     which the <see cref="AggregateExportProvider"/> aggregates.
        /// </value>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="AggregateExportProvider"/> has been disposed of.
        /// </exception>
        public ReadOnlyCollection<ExportProvider> Providers
        {
            get
            {
                ThrowIfDisposed();
                Debug.Assert(_readOnlyProviders != null);
 
                return _readOnlyProviders;
            }
        }
 
        /// <summary>
        /// Returns all exports that match the conditions of the specified import.
        /// </summary>
        /// <param name="definition">The <see cref="ImportDefinition"/> that defines the conditions of the
        /// <see cref="Export"/> to get.</param>
        /// <param name="atomicComposition">The transactional container for the composition.</param>
        /// <returns></returns>
        /// <result>
        /// An <see cref="IEnumerable{T}"/> of <see cref="Export"/> objects that match
        /// the conditions defined by <see cref="ImportDefinition"/>, if found; otherwise, an
        /// empty <see cref="IEnumerable{T}"/>.
        /// </result>
        /// <remarks>
        /// <note type="inheritinfo">
        /// The implementers should not treat the cardinality-related mismatches as errors, and are not
        /// expected to throw exceptions in those cases.
        /// For instance, if the import requests exactly one export and the provider has no matching exports or more than one,
        /// it should return an empty <see cref="IEnumerable{T}"/> of <see cref="Export"/>.
        /// </note>
        /// </remarks>
        protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition? atomicComposition)
        {
            ThrowIfDisposed();
 
            if (definition.Cardinality == ImportCardinality.ZeroOrMore)
            {
                var exports = new List<Export>();
                foreach (var provider in _providers)
                {
                    foreach (var export in provider.GetExports(definition, atomicComposition))
                    {
                        exports.Add(export);
                    }
                }
                return exports;
            }
            else
            {
                IEnumerable<Export>? allExports = null;
 
                // if asked for "one or less", the prioriry is at play - the first provider that agrees to return the value
                // which best complies with the request, wins.
                foreach (ExportProvider provider in _providers)
                {
                    bool cardinalityCheckResult = provider.TryGetExports(definition, atomicComposition, out IEnumerable<Export>? exports);
                    Debug.Assert(exports != null);
                    bool anyExports = exports.Any();
                    if (cardinalityCheckResult && anyExports)
                    {
                        // NOTE : if the provider returned nothing, we need to proceed, even if it indicated that the
                        // cardinality is correct - when asked for "one or less", the provider might - correctly -
                        // return an empty sequence, but we shouldn't be satisfied with that as providers down the list
                        // might have a value we are interested in.
                        return exports;
                    }
                    else
                    {
                        // This is a sneaky thing that we do - if in the end no provider returns the exports with the right cardinality
                        // we simply return the aggregation of all exports they have returned. This way the end result is still not what we want
                        // but no information is lost.
                        if (anyExports)
                        {
                            allExports = (allExports != null) ? allExports.Concat(exports) : exports;
                        }
                    }
                }
 
                return allExports!;
            }
        }
 
        private void OnExportChangedInternal(object? sender, ExportsChangeEventArgs e)
        {
            OnExportsChanged(e);
        }
 
        private void OnExportChangingInternal(object? sender, ExportsChangeEventArgs e)
        {
            OnExportsChanging(e);
        }
 
        [DebuggerStepThrough]
        private void ThrowIfDisposed()
        {
            if (_isDisposed == 1)
            {
                throw ExceptionBuilder.CreateObjectDisposed(this);
            }
        }
    }
}