| File: System\ComponentModel\Composition\Hosting\AggregateExportProvider.cs | Web Access |
| Project: src\runtime\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); } } } }