| File: System\ComponentModel\Composition\Hosting\ComposablePartExportProvider.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.ComponentModel.Composition.Primitives; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Threading; using Microsoft.Internal; namespace System.ComponentModel.Composition.Hosting { public class ComposablePartExportProvider : ExportProvider, IDisposable { private List<ComposablePart> _parts = new List<ComposablePart>(); private volatile bool _isDisposed; private volatile bool _isRunning; private readonly CompositionLock _lock; private ExportProvider? _sourceProvider; private ImportEngine? _importEngine; private volatile bool _currentlyComposing; private readonly CompositionOptions _compositionOptions; /// <summary> /// Initializes a new instance of the <see cref="ComposablePartExportProvider"/> class. /// </summary> public ComposablePartExportProvider() : this(false) { } public ComposablePartExportProvider(bool isThreadSafe) : this(isThreadSafe ? CompositionOptions.IsThreadSafe : CompositionOptions.Default) { } public ComposablePartExportProvider(CompositionOptions compositionOptions) { if (compositionOptions > (CompositionOptions.DisableSilentRejection | CompositionOptions.IsThreadSafe | CompositionOptions.ExportCompositionService)) { throw new ArgumentOutOfRangeException(nameof(compositionOptions)); } _compositionOptions = compositionOptions; _lock = new CompositionLock(compositionOptions.HasFlag(CompositionOptions.IsThreadSafe)); } /// <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 (!_isDisposed) { bool disposeLock = false; ImportEngine? importEngine = null; try { using (_lock.LockStateForWrite()) { if (!_isDisposed) { importEngine = _importEngine; _importEngine = null; _sourceProvider = null; _isDisposed = true; disposeLock = true; } } } finally { importEngine?.Dispose(); if (disposeLock) { _lock.Dispose(); } } } } } /// <summary> /// Gets the export provider which provides the provider access to /// exports. /// </summary> /// <value> /// The <see cref="ExportProvider"/> which provides the /// <see cref="ComposablePartExportProvider"/> access to <see cref="Export"/> objects. /// The default is <see langword="null"/>. /// </value> /// <exception cref="ArgumentNullException"> /// <paramref name="value"/> is <see langword="null"/>. /// </exception> /// <exception cref="InvalidOperationException"> /// This property has already been set. /// <para> /// -or- /// </para> /// The methods on the <see cref="ComposablePartExportProvider"/> /// have already been accessed. /// </exception> /// <exception cref="ObjectDisposedException"> /// The <see cref="ComposablePartExportProvider"/> has been disposed of. /// </exception> /// <remarks> /// This property must be set before accessing any methods on the /// <see cref="ComposablePartExportProvider"/>. /// </remarks> [DisallowNull] public ExportProvider? SourceProvider { get { ThrowIfDisposed(); return _sourceProvider; } set { ThrowIfDisposed(); Requires.NotNull(value!, nameof(value)); using (_lock.LockStateForWrite()) { EnsureCanSet(_sourceProvider); _sourceProvider = value; } } } private ImportEngine ImportEngine { get { if (_importEngine == null) { if (_sourceProvider == null) { throw new Exception(SR.Diagnostic_InternalExceptionMessage); } ImportEngine? importEngine = new ImportEngine(_sourceProvider, _compositionOptions); using (_lock.LockStateForWrite()) { if (_importEngine == null) { Thread.MemoryBarrier(); _importEngine = importEngine; importEngine = null; } } // if we have created an engine and didn't set it because of a race condition, we need to dispose of it importEngine?.Dispose(); } return _importEngine; } } /// <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(); EnsureRunning(); // Determine whether there is a composition atomicComposition-specific list of parts to use, // failing that use the usual list. We never change the list of parts in place, // but rather copy, change and write a new list atomically. Therefore all we need // to do here is to read the _parts member. List<ComposablePart>? parts = null; using (_lock.LockStateForRead()) { parts = atomicComposition.GetValueAllowNull(this, _parts); } if (parts.Count == 0) { return null; } List<Export> exports = new List<Export>(); foreach (var part in parts) { foreach (var exportDefinition in part.ExportDefinitions) { if (definition.IsConstraintSatisfiedBy(exportDefinition)) { exports.Add(CreateExport(part, exportDefinition)); } } } return exports; } public void Compose(CompositionBatch batch) { ThrowIfDisposed(); EnsureRunning(); Requires.NotNull(batch, nameof(batch)); // Quick exit test can be done prior to cloning since it's just an optimization, not a // change in behavior if ((batch.PartsToAdd.Count == 0) && (batch.PartsToRemove.Count == 0)) { return; } CompositionResult result = CompositionResult.SucceededResult; // Get updated parts list and a cloned batch var newParts = GetUpdatedPartsList(ref batch); // Allow only recursive calls from the import engine to see the changes until // they've been verified ... using (var atomicComposition = new AtomicComposition()) { // Don't allow reentrant calls to compose during previewing to prevent // corrupted state. if (_currentlyComposing) { throw new InvalidOperationException(SR.ReentrantCompose); } _currentlyComposing = true; try { // In the meantime recursive calls need to be able to see the list as well atomicComposition.SetValue(this, newParts); // Recompose any existing imports effected by the these changes first so that // adapters, resurrected parts, etc. can all play their role in satisfying // imports for added parts Recompose(batch, atomicComposition); // Ensure that required imports can be satisfied foreach (ComposablePart part in batch.PartsToAdd) { // collect the result of previewing all the adds in the batch try { ImportEngine.PreviewImports(part, atomicComposition); } catch (ChangeRejectedException ex) { result = result.MergeResult(new CompositionResult(ex.Errors)); } } result.ThrowOnErrors(atomicComposition); // Complete the new parts since they passed previewing.` using (_lock.LockStateForWrite()) { _parts = newParts; } atomicComposition.Complete(); } finally { _currentlyComposing = false; } } // Satisfy Imports // - Satisfy imports on all newly added component parts foreach (ComposablePart part in batch.PartsToAdd) { result = result.MergeResult(CompositionServices.TryInvoke(() => ImportEngine.SatisfyImports(part))); } // return errors result.ThrowOnErrors(); } private List<ComposablePart> GetUpdatedPartsList(ref CompositionBatch batch) { ArgumentNullException.ThrowIfNull(batch); // Copy the current list of parts - we are about to modify it // This is an OK thing to do as this is the only method that can modify the List AND Compose can // only be executed on one thread at a time - thus two different threads cannot tramp over each other List<ComposablePart>? parts = null; using (_lock.LockStateForRead()) { parts = _parts.ToList(); // this copies the list } foreach (ComposablePart part in batch.PartsToAdd) { parts.Add(part); } List<ComposablePart>? partsToRemove = null; foreach (ComposablePart part in batch.PartsToRemove) { if (parts.Remove(part)) { partsToRemove ??= new List<ComposablePart>(); partsToRemove.Add(part); } } // Clone the batch, so that the external changes wouldn't happen half-way thorugh compose // NOTE : this does not guarantee the atomicity of cloning, which is not the goal anyway, // rather the fact that all subsequent calls will deal with an unchanging batch batch = new CompositionBatch(batch.PartsToAdd, partsToRemove); return parts; } private void Recompose(CompositionBatch batch, AtomicComposition atomicComposition) { ArgumentNullException.ThrowIfNull(batch); // Unregister any removed component parts foreach (ComposablePart part in batch.PartsToRemove) { ImportEngine.ReleaseImports(part, atomicComposition); } // Recompose any imports effected by the these changes (the changes are // observable through GetExports in the appropriate atomicComposition, thus we can fire // the event IEnumerable<ExportDefinition> addedExports = batch.PartsToAdd.Count != 0 ? batch.PartsToAdd.SelectMany(part => part.ExportDefinitions).ToArray() : Array.Empty<ExportDefinition>(); IEnumerable<ExportDefinition> removedExports = batch.PartsToRemove.Count != 0 ? batch.PartsToRemove.SelectMany(part => part.ExportDefinitions).ToArray() : Array.Empty<ExportDefinition>(); OnExportsChanging( new ExportsChangeEventArgs(addedExports, removedExports, atomicComposition)); atomicComposition.AddCompleteAction(() => OnExportsChanged( new ExportsChangeEventArgs(addedExports, removedExports, null))); } private Export CreateExport(ComposablePart part, ExportDefinition export) { return new Export(export, () => GetExportedValue(part, export)); } private object? GetExportedValue(ComposablePart part, ExportDefinition export) { ThrowIfDisposed(); EnsureRunning(); return CompositionServices.GetExportedValueFromComposedPart(ImportEngine, part, export); } [DebuggerStepThrough] private void ThrowIfDisposed() { if (_isDisposed) { throw new ObjectDisposedException(GetType().Name); } } [DebuggerStepThrough] private void EnsureCanRun() { if (_sourceProvider == null) { throw new InvalidOperationException(SR.Format(SR.ObjectMustBeInitialized, "SourceProvider")); // NOLOC } } [DebuggerStepThrough] private void EnsureRunning() { if (!_isRunning) { using (_lock.LockStateForWrite()) { if (!_isRunning) { EnsureCanRun(); _isRunning = true; } } } } [DebuggerStepThrough] private void EnsureCanSet<T>(T? currentValue) where T : class { if ((_isRunning) || (currentValue != null)) { throw new InvalidOperationException(SR.ObjectAlreadyInitialized); } } } }