File: System\ComponentModel\Composition\Hosting\ComposablePartCatalogCollection.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;
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);
            }
        }
    }
}