| File: System\ComponentModel\Composition\Hosting\ComposablePartCatalogCollection.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; using System.Collections.Generic; using System.ComponentModel.Composition.Primitives; using System.Linq; using System.Threading; using Microsoft.Internal; using Microsoft.Internal.Collections; namespace System.ComponentModel.Composition.Hosting { /// <summary> /// This class implements a threadsafe ICollection{T} of ComposablePartCatalog. /// It is exposed as an ICollection(ComposablePartCatalog) /// It is threadsafe, notifications are not marshalled using a SynchronizationContext. /// It is Disposable. /// </summary> internal sealed class ComposablePartCatalogCollection : ICollection<ComposablePartCatalog>, INotifyComposablePartCatalogChanged, IDisposable { private readonly ReadWriteLock _lock = new ReadWriteLock(); private readonly Action<ComposablePartCatalogChangeEventArgs>? _onChanged; private readonly Action<ComposablePartCatalogChangeEventArgs>? _onChanging; private List<ComposablePartCatalog> _catalogs = new List<ComposablePartCatalog>(); private volatile bool _isCopyNeeded; private volatile bool _isDisposed; private bool _hasChanged; public ComposablePartCatalogCollection( IEnumerable<ComposablePartCatalog>? catalogs, Action<ComposablePartCatalogChangeEventArgs>? onChanged, Action<ComposablePartCatalogChangeEventArgs>? onChanging) { catalogs ??= Enumerable.Empty<ComposablePartCatalog>(); _catalogs = new List<ComposablePartCatalog>(catalogs); _onChanged = onChanged; _onChanging = onChanging; SubscribeToCatalogNotifications(catalogs); } public void Add(ComposablePartCatalog item) { Requires.NotNull(item, nameof(item)); ThrowIfDisposed(); var addedParts = new Lazy<IEnumerable<ComposablePartDefinition>>(() => item.ToArray(), LazyThreadSafetyMode.PublicationOnly); using (var atomicComposition = new AtomicComposition()) { RaiseChangingEvent(addedParts, null, atomicComposition); using (new WriteLock(_lock)) { if (_isCopyNeeded) { _catalogs = new List<ComposablePartCatalog>(_catalogs); _isCopyNeeded = false; } _hasChanged = true; _catalogs.Add(item); } SubscribeToCatalogNotifications(item); // Complete after the catalog changes are written atomicComposition.Complete(); } RaiseChangedEvent(addedParts, null); } /// <summary> /// Notify when the contents of the Catalog has changed. /// </summary> public event EventHandler<ComposablePartCatalogChangeEventArgs>? Changed; /// <summary> /// Notify when the contents of the Catalog has changing. /// </summary> public event EventHandler<ComposablePartCatalogChangeEventArgs>? Changing; public void Clear() { ThrowIfDisposed(); // No action is required if we are already empty ComposablePartCatalog[]? catalogs = null; using (new ReadLock(_lock)) { if (_catalogs.Count == 0) { return; } catalogs = _catalogs.ToArray(); } // We are doing this outside of the lock, so it's possible that the catalog will continute propagating events from things // we are about to unsubscribe from. Given the non-specificity of our event, in the worst case scenario we would simply fire // unnecessary events. var removedParts = new Lazy<IEnumerable<ComposablePartDefinition>>(() => catalogs.SelectMany(catalog => catalog).ToArray(), LazyThreadSafetyMode.PublicationOnly); // Validate the changes before applying them using (var atomicComposition = new AtomicComposition()) { RaiseChangingEvent(null, removedParts, atomicComposition); UnsubscribeFromCatalogNotifications(catalogs); using (new WriteLock(_lock)) { _catalogs = new List<ComposablePartCatalog>(); _isCopyNeeded = false; _hasChanged = true; } // Complete after the catalog changes are written atomicComposition.Complete(); } RaiseChangedEvent(null, removedParts); } public bool Contains(ComposablePartCatalog item) { Requires.NotNull(item, nameof(item)); ThrowIfDisposed(); using (new ReadLock(_lock)) { return _catalogs.Contains(item); } } public void CopyTo(ComposablePartCatalog[] array, int arrayIndex) { ThrowIfDisposed(); using (new ReadLock(_lock)) { _catalogs.CopyTo(array, arrayIndex); } } public int Count { get { ThrowIfDisposed(); using (new ReadLock(_lock)) { return _catalogs.Count; } } } public bool IsReadOnly { get { ThrowIfDisposed(); return false; } } public bool Remove(ComposablePartCatalog item) { Requires.NotNull(item, nameof(item)); ThrowIfDisposed(); using (new ReadLock(_lock)) { if (!_catalogs.Contains(item)) { return false; } } bool isSuccessfulRemoval = false; var removedParts = new Lazy<IEnumerable<ComposablePartDefinition>>(() => item.ToArray(), LazyThreadSafetyMode.PublicationOnly); using (var atomicComposition = new AtomicComposition()) { RaiseChangingEvent(null, removedParts, atomicComposition); using (new WriteLock(_lock)) { if (_isCopyNeeded) { _catalogs = new List<ComposablePartCatalog>(_catalogs); _isCopyNeeded = false; } isSuccessfulRemoval = _catalogs.Remove(item); if (isSuccessfulRemoval) { _hasChanged = true; } } UnsubscribeFromCatalogNotifications(item); // Complete after the catalog changes are written atomicComposition.Complete(); } RaiseChangedEvent(null, removedParts); return isSuccessfulRemoval; } internal bool HasChanged { get { ThrowIfDisposed(); using (new ReadLock(_lock)) { return _hasChanged; } } } public IEnumerator<ComposablePartCatalog> GetEnumerator() { ThrowIfDisposed(); using (new WriteLock(_lock)) { IEnumerator<ComposablePartCatalog> enumerator = _catalogs.GetEnumerator(); _isCopyNeeded = true; return enumerator; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { if (!_isDisposed) { bool disposeLock = false; IEnumerable<ComposablePartCatalog>? catalogs = null; try { using (new WriteLock(_lock)) { if (!_isDisposed) { disposeLock = true; catalogs = _catalogs; _catalogs = null!; _isDisposed = true; } } } finally { if (catalogs != null) { UnsubscribeFromCatalogNotifications(catalogs); catalogs.ForEach(catalog => catalog.Dispose()); } if (disposeLock) { _lock.Dispose(); } } } } } private void RaiseChangedEvent( Lazy<IEnumerable<ComposablePartDefinition>>? addedDefinitions, Lazy<IEnumerable<ComposablePartDefinition>>? removedDefinitions) { if (_onChanged == null || Changed == null) { return; } var added = (addedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : addedDefinitions.Value); var removed = (removedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : removedDefinitions.Value); _onChanged.Invoke(new ComposablePartCatalogChangeEventArgs(added, removed, null)); } public void OnChanged(object sender, ComposablePartCatalogChangeEventArgs e) { Changed?.Invoke(sender, e); } private void RaiseChangingEvent( Lazy<IEnumerable<ComposablePartDefinition>>? addedDefinitions, Lazy<IEnumerable<ComposablePartDefinition>>? removedDefinitions, AtomicComposition? atomicComposition) { if (_onChanging == null || Changing == null) { return; } var added = (addedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : addedDefinitions.Value); var removed = (removedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : removedDefinitions.Value); _onChanging.Invoke(new ComposablePartCatalogChangeEventArgs(added, removed, atomicComposition)); } public void OnChanging(object sender, ComposablePartCatalogChangeEventArgs e) { Changing?.Invoke(sender, e); } private void OnContainedCatalogChanged(object? sender, ComposablePartCatalogChangeEventArgs e) { if (_onChanged == null || Changed == null) { return; } _onChanged.Invoke(e); } private void OnContainedCatalogChanging(object? sender, ComposablePartCatalogChangeEventArgs e) { if (_onChanging == null || Changing == null) { return; } _onChanging.Invoke(e); } private void SubscribeToCatalogNotifications(ComposablePartCatalog catalog) { if (catalog is INotifyComposablePartCatalogChanged notifyCatalog) { notifyCatalog.Changed += OnContainedCatalogChanged; notifyCatalog.Changing += OnContainedCatalogChanging; } } private void SubscribeToCatalogNotifications(IEnumerable<ComposablePartCatalog> catalogs) { foreach (var catalog in catalogs) { SubscribeToCatalogNotifications(catalog); } } private void UnsubscribeFromCatalogNotifications(ComposablePartCatalog catalog) { if (catalog is INotifyComposablePartCatalogChanged notifyCatalog) { notifyCatalog.Changed -= OnContainedCatalogChanged; notifyCatalog.Changing -= OnContainedCatalogChanging; } } private void UnsubscribeFromCatalogNotifications(IEnumerable<ComposablePartCatalog> catalogs) { foreach (var catalog in catalogs) { UnsubscribeFromCatalogNotifications(catalog); } } private void ThrowIfDisposed() { if (_isDisposed) { throw ExceptionBuilder.CreateObjectDisposed(this); } } } }