|
// 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.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.Internal;
namespace System.ComponentModel.Composition.Hosting
{
public partial class CompositionContainer : ExportProvider, ICompositionService, IDisposable
{
private readonly CompositionOptions _compositionOptions;
private ImportEngine? _importEngine;
private ComposablePartExportProvider _partExportProvider;
private ExportProvider _rootProvider;
private IDisposable? _disposableRootProvider;
private CatalogExportProvider? _catalogExportProvider;
private ExportProvider _localExportProvider;
private IDisposable? _disposableLocalExportProvider;
private ExportProvider? _ancestorExportProvider;
private IDisposable? _disposableAncestorExportProvider;
private readonly ReadOnlyCollection<ExportProvider> _providers;
private volatile bool _isDisposed;
private readonly object _lock = new object();
private static readonly ReadOnlyCollection<ExportProvider> EmptyProviders = new ReadOnlyCollection<ExportProvider>(Array.Empty<ExportProvider>());
/// <summary>
/// Initializes a new instance of the <see cref="CompositionContainer"/> class.
/// </summary>
public CompositionContainer()
: this((ComposablePartCatalog?)null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CompositionContainer"/> class
/// with the specified export providers.
/// </summary>
/// <param name="providers">
/// A <see cref="Array"/> of <see cref="ExportProvider"/> objects which provide
/// the <see cref="CompositionContainer"/> access to <see cref="Export"/> objects,
/// or <see langword="null"/> to set <see cref="Providers"/> to an empty
/// <see cref="ReadOnlyCollection{T}"/>.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="providers"/> contains an element that is <see langword="null"/>.
/// </exception>
public CompositionContainer(params ExportProvider[]? providers) :
this((ComposablePartCatalog?)null, providers)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CompositionContainer"/> class
/// with the specified export providers.
/// </summary>
/// <param name="compositionOptions">
/// <see cref="CompositionOptions"/> enumeration with flags controlling the composition.
/// </param>
/// <param name="providers">
/// A <see cref="Array"/> of <see cref="ExportProvider"/> objects which provide
/// the <see cref="CompositionContainer"/> access to <see cref="Export"/> objects,
/// or <see langword="null"/> to set <see cref="Providers"/> to an empty
/// <see cref="ReadOnlyCollection{T}"/>.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="providers"/> contains an element that is <see langword="null"/>.
/// </exception>
public CompositionContainer(CompositionOptions compositionOptions, params ExportProvider[]? providers) :
this((ComposablePartCatalog?)null, compositionOptions, providers)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CompositionContainer"/> class
/// with the specified catalog and export providers.
/// </summary>
/// <param name="catalog">A catalog that provides <see cref="Export"/> objects to the <see cref="CompositionContainer"/>.</param>
/// <param name="providers">
/// A <see cref="Array"/> of <see cref="ExportProvider"/> objects which provide
/// the <see cref="CompositionContainer"/> access to <see cref="Export"/> objects,
/// or <see langword="null"/> to set <see cref="Providers"/> to an empty
/// <see cref="ReadOnlyCollection{T}"/>.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="providers"/> contains an element that is <see langword="null"/>.
/// </exception>
public CompositionContainer(ComposablePartCatalog? catalog, params ExportProvider[]? providers) :
this(catalog, false, providers)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CompositionContainer"/> class
/// with the specified catalog and export providers.
/// </summary>
/// <param name="catalog">A catalog that provides <see cref="Export"/> objects to the <see cref="CompositionContainer"/>.</param>
/// <param name="isThreadSafe">
/// <see cref="bool"/> indicates whether container instances are threadsafe.
/// </param>
/// <param name="providers">
/// A <see cref="Array"/> of <see cref="ExportProvider"/> objects which provide
/// the <see cref="CompositionContainer"/> access to <see cref="Export"/> objects,
/// or <see langword="null"/> to set <see cref="Providers"/> to an empty
/// <see cref="ReadOnlyCollection{T}"/>.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="providers"/> contains an element that is <see langword="null"/>.
/// </exception>
public CompositionContainer(ComposablePartCatalog? catalog, bool isThreadSafe, params ExportProvider[]? providers)
: this(catalog, isThreadSafe ? CompositionOptions.IsThreadSafe : CompositionOptions.Default, providers)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CompositionContainer"/> class
/// with the specified catalog and export providers.
/// </summary>
/// <param name="catalog">A catalog that provides <see cref="Export"/> objects to the <see cref="CompositionContainer"/>.</param>
/// <param name="compositionOptions">
/// <see cref="CompositionOptions"/> enumeration with flags controlling the composition.
/// </param>
/// <param name="providers">
/// A <see cref="Array"/> of <see cref="ExportProvider"/> objects which provide
/// the <see cref="CompositionContainer"/> access to <see cref="Export"/> objects,
/// or <see langword="null"/> to set <see cref="Providers"/> to an empty
/// <see cref="ReadOnlyCollection{T}"/>.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="providers"/> contains an element that is <see langword="null"/>.
/// </exception>
public CompositionContainer(ComposablePartCatalog? catalog, CompositionOptions compositionOptions, params ExportProvider[]? providers)
{
if (compositionOptions > (CompositionOptions.DisableSilentRejection | CompositionOptions.IsThreadSafe | CompositionOptions.ExportCompositionService))
{
throw new ArgumentOutOfRangeException(nameof(compositionOptions));
}
_compositionOptions = compositionOptions;
// We always create the mutable provider
_partExportProvider = new ComposablePartExportProvider(compositionOptions);
_partExportProvider.SourceProvider = this;
// Create the catalog export provider, only if necessary
if (catalog != null)
{
_catalogExportProvider = new CatalogExportProvider(catalog, compositionOptions);
_catalogExportProvider.SourceProvider = this;
}
// Set the local export provider
if (_catalogExportProvider != null)
{
_localExportProvider = new AggregateExportProvider(_partExportProvider, _catalogExportProvider);
_disposableLocalExportProvider = _localExportProvider as IDisposable;
}
else
{
_localExportProvider = _partExportProvider;
}
// Set the ancestor export provider, if ancestors are supplied
if ((providers != null) && (providers.Length > 0))
{
// Aggregate ancestors if and only if more than one passed
if (providers.Length > 1)
{
_ancestorExportProvider = new AggregateExportProvider(providers);
_disposableAncestorExportProvider = _ancestorExportProvider as IDisposable;
}
else
{
if (providers[0] == null)
{
throw ExceptionBuilder.CreateContainsNullElement(nameof(providers));
}
_ancestorExportProvider = providers[0];
}
}
// finally set the root provider
if (_ancestorExportProvider == null)
{
// if no ancestors are passed, the local and the root are the same
_rootProvider = _localExportProvider;
}
else
{
int exportProviderCount = 1 + ((catalog != null) ? 1 : 0) + ((providers != null) ? providers.Length : 0);
ExportProvider[] rootProviders = new ExportProvider[exportProviderCount];
rootProviders[0] = _partExportProvider;
int customProviderStartIndex = 1;
if (catalog != null)
{
rootProviders[1] = _catalogExportProvider!;
customProviderStartIndex = 2;
}
if (providers != null)
{
for (int i = 0; i < providers.Length; i++)
{
rootProviders[customProviderStartIndex + i] = providers[i];
}
}
_rootProvider = new AggregateExportProvider(rootProviders);
_disposableRootProvider = _rootProvider as IDisposable;
}
//Insert Composition Service
if (compositionOptions.HasFlag(CompositionOptions.ExportCompositionService))
{
this.ComposeExportedValue<ICompositionService>(new CompositionServiceShim(this));
}
_rootProvider.ExportsChanged += OnExportsChangedInternal;
_rootProvider.ExportsChanging += OnExportsChangingInternal;
_providers = (providers != null) ? Array.AsReadOnly((ExportProvider[])providers.Clone()) : EmptyProviders;
}
internal CompositionOptions CompositionOptions
{
get
{
ThrowIfDisposed();
return _compositionOptions;
}
}
/// <summary>
/// Gets the catalog which provides the container access to exports produced
/// from composable parts.
/// </summary>
/// <value>
/// The <see cref="ComposablePartCatalog"/> which provides the
/// <see cref="CompositionContainer"/> access to exports produced from
/// <see cref="ComposablePart"/> objects. The default is <see langword="null"/>.
/// </value>
/// <exception cref="ObjectDisposedException">
/// The <see cref="CompositionContainer"/> has been disposed of.
/// </exception>
public ComposablePartCatalog? Catalog
{
get
{
ThrowIfDisposed();
return _catalogExportProvider?.Catalog;
}
}
internal CatalogExportProvider? CatalogExportProvider
{
get
{
ThrowIfDisposed();
return _catalogExportProvider;
}
}
/// <summary>
/// Gets the export providers which provide the container access to additional exports.
/// </summary>
/// <value>
/// A <see cref="ReadOnlyCollection{T}"/> of <see cref="ExportProvider"/> objects
/// which provide the <see cref="CompositionContainer"/> access to additional
/// <see cref="Export"/> objects. The default is an empty
/// <see cref="ReadOnlyCollection{T}"/>.
/// </value>
/// <exception cref="ObjectDisposedException">
/// The <see cref="CompositionContainer"/> has been disposed of.
/// </exception>
public ReadOnlyCollection<ExportProvider> Providers
{
get
{
ThrowIfDisposed();
Debug.Assert(_providers != null);
return _providers;
}
}
/// <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)
{
ExportProvider? rootProvider = null;
IDisposable? disposableAncestorExportProvider = null;
IDisposable? disposableLocalExportProvider = null;
IDisposable? disposableRootProvider = null;
ComposablePartExportProvider? partExportProvider = null;
CatalogExportProvider? catalogExportProvider = null;
ImportEngine? importEngine = null;
lock (_lock)
{
if (!_isDisposed)
{
rootProvider = _rootProvider;
_rootProvider = null!;
disposableRootProvider = _disposableRootProvider;
_disposableRootProvider = null;
disposableLocalExportProvider = _disposableLocalExportProvider;
_disposableLocalExportProvider = null;
_localExportProvider = null!;
disposableAncestorExportProvider = _disposableAncestorExportProvider;
_disposableAncestorExportProvider = null;
_ancestorExportProvider = null;
partExportProvider = _partExportProvider;
_partExportProvider = null!;
catalogExportProvider = _catalogExportProvider;
_catalogExportProvider = null;
importEngine = _importEngine;
_importEngine = null;
_isDisposed = true;
}
}
if (rootProvider != null)
{
rootProvider.ExportsChanged -= OnExportsChangedInternal;
rootProvider.ExportsChanging -= OnExportsChangingInternal;
}
disposableRootProvider?.Dispose();
disposableAncestorExportProvider?.Dispose();
disposableLocalExportProvider?.Dispose();
catalogExportProvider?.Dispose();
partExportProvider?.Dispose();
importEngine?.Dispose();
}
}
}
public void Compose(CompositionBatch batch)
{
Requires.NotNull(batch, nameof(batch));
ThrowIfDisposed();
_partExportProvider.Compose(batch);
}
/// <summary>
/// Releases the <see cref="Export"/> from the <see cref="CompositionContainer"/>. The behavior
/// may vary depending on the implementation of the <see cref="ExportProvider"/> that produced
/// the <see cref="Export"/> instance. As a general rule non shared exports should be early
/// released causing them to be detached from the container.
///
/// For example the <see cref="CatalogExportProvider"/> will only release
/// an <see cref="Export"/> if it comes from a <see cref="ComposablePart"/> that was constructed
/// under a <see cref="CreationPolicy.NonShared" /> context. Release in this context means walking
/// the dependency chain of the <see cref="Export"/>s, detaching references from the container and
/// calling Dispose on the <see cref="ComposablePart"/>s as needed. If the <see cref="Export"/>
/// was constructed under a <see cref="CreationPolicy.Shared" /> context the
/// <see cref="CatalogExportProvider"/> will do nothing as it may be in use by other requestors.
/// Those will only be detached when the container is itself disposed.
/// </summary>
/// <param name="export"><see cref="Export"/> that needs to be released.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="export"/> is <see langword="null"/>.
/// </exception>
public void ReleaseExport(Export export)
{
Requires.NotNull(export, nameof(export));
if (export is IDisposable dependency)
{
dependency.Dispose();
}
}
/// <summary>
/// Releases the <see cref="Lazy{T}"/> from the <see cref="CompositionContainer"/>. The behavior
/// may vary depending on the implementation of the <see cref="ExportProvider"/> that produced
/// the <see cref="Export"/> instance. As a general rule non shared exports should be early
/// released causing them to be detached from the container.
///
/// For example the <see cref="CatalogExportProvider"/> will only release
/// an <see cref="Lazy{T}"/> if it comes from a <see cref="ComposablePart"/> that was constructed
/// under a <see cref="CreationPolicy.NonShared" /> context. Release in this context means walking
/// the dependency chain of the <see cref="Export"/>s, detaching references from the container and
/// calling Dispose on the <see cref="ComposablePart"/>s as needed. If the <see cref="Export"/>
/// was constructed under a <see cref="CreationPolicy.Shared" /> context the
/// <see cref="CatalogExportProvider"/> will do nothing as it may be in use by other requestors.
/// Those will only be detached when the container is itself disposed.
/// </summary>
/// <param name="export"><see cref="Export"/> that needs to be released.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="export"/> is <see langword="null"/>.
/// </exception>
public void ReleaseExport<T>(Lazy<T> export)
{
Requires.NotNull(export, nameof(export));
if (export is IDisposable dependency)
{
dependency.Dispose();
}
}
/// <summary>
/// Releases a set of <see cref="Export"/>s from the <see cref="CompositionContainer"/>.
/// See also <see cref="ReleaseExport"/>.
/// </summary>
/// <param name="exports"><see cref="Export"/>s that need to be released.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="exports"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="exports"/> contains an element that is <see langword="null"/>.
/// </exception>
public void ReleaseExports(IEnumerable<Export> exports)
{
Requires.NotNullOrNullElements(exports, nameof(exports));
foreach (Export export in exports)
{
ReleaseExport(export);
}
}
/// <summary>
/// Releases a set of <see cref="Export"/>s from the <see cref="CompositionContainer"/>.
/// See also <see cref="ReleaseExport"/>.
/// </summary>
/// <param name="exports"><see cref="Export"/>s that need to be released.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="exports"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="exports"/> contains an element that is <see langword="null"/>.
/// </exception>
public void ReleaseExports<T>(IEnumerable<Lazy<T>> exports)
{
Requires.NotNullOrNullElements(exports, nameof(exports));
foreach (Lazy<T> export in exports)
{
ReleaseExport(export);
}
}
/// <summary>
/// Releases a set of <see cref="Export"/>s from the <see cref="CompositionContainer"/>.
/// See also <see cref="ReleaseExport"/>.
/// </summary>
/// <param name="exports"><see cref="Export"/>s that need to be released.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="exports"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="exports"/> contains an element that is <see langword="null"/>.
/// </exception>
public void ReleaseExports<T, TMetadataView>(IEnumerable<Lazy<T, TMetadataView>> exports)
{
Requires.NotNullOrNullElements(exports, nameof(exports));
foreach (Lazy<T, TMetadataView> export in exports)
{
ReleaseExport(export);
}
}
/// <summary>
/// Sets the imports of the specified composable part exactly once and they will not
/// ever be recomposed.
/// </summary>
/// <param name="part">
/// The <see cref="ComposablePart"/> to set the imports.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="part"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="CompositionException">
/// An error occurred during composition. <see cref="CompositionException.Errors"/> will
/// contain a collection of errors that occurred.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// The <see cref="ICompositionService"/> has been disposed of.
/// </exception>
public void SatisfyImportsOnce(ComposablePart part)
{
ThrowIfDisposed();
if (_importEngine == null)
{
ImportEngine? importEngine = new ImportEngine(this, _compositionOptions);
lock (_lock)
{
if (_importEngine == null)
{
Thread.MemoryBarrier();
_importEngine = importEngine;
importEngine = null;
}
}
importEngine?.Dispose();
}
_importEngine.SatisfyImportsOnce(part);
}
internal void OnExportsChangedInternal(object? sender, ExportsChangeEventArgs e)
{
OnExportsChanged(e);
}
internal void OnExportsChangingInternal(object? sender, ExportsChangeEventArgs e)
{
OnExportsChanging(e);
}
/// <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();
IEnumerable<Export>? exports = null;
if (!definition.Metadata.TryGetValue(CompositionConstants.ImportSourceMetadataName, out object? source))
{
source = ImportSource.Any;
}
switch ((ImportSource)source!)
{
case ImportSource.Any:
if (_rootProvider == null)
{
throw new Exception(SR.Diagnostic_InternalExceptionMessage);
}
_rootProvider.TryGetExports(definition, atomicComposition, out exports);
break;
case ImportSource.Local:
if (_localExportProvider == null)
{
throw new Exception(SR.Diagnostic_InternalExceptionMessage);
}
_localExportProvider.TryGetExports(definition.RemoveImportSource(), atomicComposition, out exports);
break;
case ImportSource.NonLocal:
_ancestorExportProvider?.TryGetExports(definition.RemoveImportSource(), atomicComposition, out exports);
break;
}
return exports;
}
[DebuggerStepThrough]
private void ThrowIfDisposed()
{
if (_isDisposed)
{
throw ExceptionBuilder.CreateObjectDisposed(this);
}
}
}
}
|