1 // -----------------------------------------------------------------------
\r
2 // Copyright (c) Microsoft Corporation. All rights reserved.
\r
3 // -----------------------------------------------------------------------
\r
5 using System.Collections.ObjectModel;
\r
6 using System.Collections.Generic;
\r
7 using System.ComponentModel.Composition.Primitives;
\r
10 using System.Threading;
\r
11 using System.Diagnostics;
\r
12 using Microsoft.Internal;
\r
13 using Microsoft.Internal.Collections;
\r
14 using System.Collections;
\r
16 namespace System.ComponentModel.Composition.Hosting
\r
19 /// This class implements a threadsafe ICollection{T} of ComposablePartCatalog.
\r
20 /// It is exposed as an ICollection(ComposablePartCatalog)
\r
21 /// It is threadsafe, notifications are not marshalled using a SynchronizationContext.
\r
22 /// It is Disposable.
\r
24 internal class ComposablePartCatalogCollection : ICollection<ComposablePartCatalog>, INotifyComposablePartCatalogChanged, IDisposable
\r
26 private readonly Lock _lock = new Lock();
\r
27 private Action<ComposablePartCatalogChangeEventArgs> _onChanged;
\r
28 private Action<ComposablePartCatalogChangeEventArgs> _onChanging;
\r
29 private List<ComposablePartCatalog> _catalogs = new List<ComposablePartCatalog>();
\r
30 private volatile bool _isCopyNeeded = false;
\r
31 private volatile bool _isDisposed = false;
\r
32 private bool _hasChanged = false;
\r
34 public ComposablePartCatalogCollection(IEnumerable<ComposablePartCatalog> catalogs)
\r
35 : this(catalogs, null, null)
\r
39 public ComposablePartCatalogCollection(
\r
40 IEnumerable<ComposablePartCatalog> catalogs,
\r
41 Action<ComposablePartCatalogChangeEventArgs> onChanged,
\r
42 Action<ComposablePartCatalogChangeEventArgs> onChanging)
\r
44 catalogs = catalogs ?? Enumerable.Empty<ComposablePartCatalog>();
\r
45 this._catalogs = new List<ComposablePartCatalog>(catalogs);
\r
46 this._onChanged = onChanged;
\r
47 this._onChanging = onChanging;
\r
49 SubscribeToCatalogNotifications(catalogs);
\r
52 public void Add(ComposablePartCatalog item)
\r
54 Requires.NotNull(item, "item");
\r
56 this.ThrowIfDisposed();
\r
58 var addedParts = new Lazy<IEnumerable<ComposablePartDefinition>>(() => item.Parts.ToArray(), false);
\r
60 using (var atomicComposition = new AtomicComposition())
\r
62 this.RaiseChangingEvent(addedParts, null, atomicComposition);
\r
64 using (new WriteLock(this._lock))
\r
66 if (this._isCopyNeeded)
\r
68 this._catalogs = new List<ComposablePartCatalog>(this._catalogs);
\r
69 this._isCopyNeeded = false;
\r
71 this._hasChanged = true;
\r
72 this._catalogs.Add(item);
\r
75 this.SubscribeToCatalogNotifications(item);
\r
77 // Complete after the catalog changes are written
\r
78 atomicComposition.Complete();
\r
81 this.RaiseChangedEvent(addedParts, null);
\r
85 /// Notify when the contents of the Catalog has changed.
\r
87 public event EventHandler<ComposablePartCatalogChangeEventArgs> Changed;
\r
90 /// Notify when the contents of the Catalog has changing.
\r
92 public event EventHandler<ComposablePartCatalogChangeEventArgs> Changing;
\r
96 this.ThrowIfDisposed();
\r
98 // No action is required if we are already empty
\r
99 ComposablePartCatalog[] catalogs = null;
\r
100 using (new ReadLock(this._lock))
\r
102 if (this._catalogs.Count == 0)
\r
106 catalogs = this._catalogs.ToArray();
\r
109 //TODO-MT: This is pretty suspect - we can easily eliminate catalogs that aren't listed as being
\r
110 // removed. Then again, the idea of trying to mutate the catalog on two threads at the same time is pretty
\r
111 // suspect to begin with. When would that ever result in a meaningful composition?
\r
113 // We are doing this outside of the lock, so it's possible that the catalog will continute propagating events from things
\r
114 // we are about to unsubscribe from. Given the non-specificity of our event, in the worst case scenario we would simply fire
\r
115 // unnecessary events.
\r
117 var removedParts = new Lazy<IEnumerable<ComposablePartDefinition>>(() => catalogs.SelectMany(catalog => catalog.Parts).ToArray(), false);
\r
119 // Validate the changes before applying them
\r
120 using (var atomicComposition = new AtomicComposition())
\r
122 this.RaiseChangingEvent(null, removedParts, atomicComposition);
\r
123 this.UnsubscribeFromCatalogNotifications(catalogs);
\r
125 using (new WriteLock(this._lock))
\r
127 this._catalogs = new List<ComposablePartCatalog>();
\r
129 this._isCopyNeeded = false;
\r
130 this._hasChanged = true;
\r
133 // Complete after the catalog changes are written
\r
134 atomicComposition.Complete();
\r
137 this.RaiseChangedEvent(null, removedParts);
\r
140 public bool Contains(ComposablePartCatalog item)
\r
142 Requires.NotNull(item, "item");
\r
144 this.ThrowIfDisposed();
\r
146 using (new ReadLock(this._lock))
\r
148 return this._catalogs.Contains(item);
\r
152 public void CopyTo(ComposablePartCatalog[] array, int arrayIndex)
\r
154 this.ThrowIfDisposed();
\r
156 using (new ReadLock(this._lock))
\r
158 this._catalogs.CopyTo(array, arrayIndex);
\r
166 this.ThrowIfDisposed();
\r
168 using (new ReadLock(this._lock))
\r
170 return this._catalogs.Count;
\r
175 public bool IsReadOnly
\r
179 this.ThrowIfDisposed();
\r
185 public bool Remove(ComposablePartCatalog item)
\r
187 Requires.NotNull(item, "item");
\r
189 this.ThrowIfDisposed();
\r
191 using (new ReadLock(this._lock))
\r
193 if (!this._catalogs.Contains(item))
\r
199 bool isSuccessfulRemoval = false;
\r
201 var removedParts = new Lazy<IEnumerable<ComposablePartDefinition>>(() => item.Parts.ToArray(), false);
\r
202 using (var atomicComposition = new AtomicComposition())
\r
204 this.RaiseChangingEvent(null, removedParts, atomicComposition);
\r
206 using (new WriteLock(this._lock))
\r
210 this._catalogs = new List<ComposablePartCatalog>(this._catalogs);
\r
211 this._isCopyNeeded = false;
\r
214 isSuccessfulRemoval = this._catalogs.Remove(item);
\r
215 if (isSuccessfulRemoval)
\r
217 this._hasChanged = true;
\r
221 this.UnsubscribeFromCatalogNotifications(item);
\r
223 // Complete after the catalog changes are written
\r
224 atomicComposition.Complete();
\r
227 this.RaiseChangedEvent(null, removedParts);
\r
229 return isSuccessfulRemoval;
\r
232 internal bool HasChanged
\r
236 this.ThrowIfDisposed();
\r
238 using (new ReadLock(this._lock))
\r
240 return this._hasChanged;
\r
245 public IEnumerator<ComposablePartCatalog> GetEnumerator()
\r
247 this.ThrowIfDisposed();
\r
249 using (new ReadLock(this._lock))
\r
251 IEnumerator<ComposablePartCatalog> enumerator = this._catalogs.GetEnumerator();
\r
252 this._isCopyNeeded = true;
\r
257 IEnumerator IEnumerable.GetEnumerator()
\r
259 return this.GetEnumerator();
\r
262 public void Dispose()
\r
265 GC.SuppressFinalize(this);
\r
268 protected virtual void Dispose(bool disposing)
\r
272 if (!this._isDisposed)
\r
274 bool disposeLock = false;
\r
275 IEnumerable<ComposablePartCatalog> catalogs = null;
\r
278 using (new WriteLock(this._lock))
\r
280 if (!this._isDisposed)
\r
282 disposeLock = true;
\r
284 catalogs = this._catalogs;
\r
285 this._catalogs = null;
\r
287 this._isDisposed = true;
\r
293 if (catalogs != null)
\r
295 this.UnsubscribeFromCatalogNotifications(catalogs);
\r
296 catalogs.ForEach(catalog => catalog.Dispose());
\r
301 this._lock.Dispose();
\r
308 private void RaiseChangedEvent(
\r
309 Lazy<IEnumerable<ComposablePartDefinition>> addedDefinitions,
\r
310 Lazy<IEnumerable<ComposablePartDefinition>> removedDefinitions)
\r
312 if (this._onChanged == null || this.Changed == null)
\r
317 var added = (addedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : addedDefinitions.Value);
\r
318 var removed = (removedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : removedDefinitions.Value);
\r
320 this._onChanged.Invoke(new ComposablePartCatalogChangeEventArgs(added, removed, null));
\r
323 public void OnChanged(object sender, ComposablePartCatalogChangeEventArgs e)
\r
325 var changedEvent = this.Changed;
\r
326 if (changedEvent != null)
\r
328 changedEvent(sender, e);
\r
332 private void RaiseChangingEvent(
\r
333 Lazy<IEnumerable<ComposablePartDefinition>> addedDefinitions,
\r
334 Lazy<IEnumerable<ComposablePartDefinition>> removedDefinitions,
\r
335 AtomicComposition atomicComposition)
\r
337 if (this._onChanging == null || this.Changing == null)
\r
341 var added = (addedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : addedDefinitions.Value);
\r
342 var removed = (removedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : removedDefinitions.Value);
\r
344 this._onChanging.Invoke(new ComposablePartCatalogChangeEventArgs(added, removed, atomicComposition));
\r
347 public void OnChanging(object sender, ComposablePartCatalogChangeEventArgs e)
\r
349 var changingEvent = this.Changing;
\r
350 if (changingEvent != null)
\r
352 changingEvent(sender, e);
\r
356 private void OnContainedCatalogChanged(object sender, ComposablePartCatalogChangeEventArgs e)
\r
358 if (this._onChanged == null || this.Changed == null)
\r
363 this._onChanged.Invoke(e);
\r
366 private void OnContainedCatalogChanging(object sender, ComposablePartCatalogChangeEventArgs e)
\r
368 if (this._onChanging == null || this.Changing == null)
\r
373 this._onChanging.Invoke(e);
\r
376 private void SubscribeToCatalogNotifications(ComposablePartCatalog catalog)
\r
378 INotifyComposablePartCatalogChanged notifyCatalog = catalog as INotifyComposablePartCatalogChanged;
\r
379 if (notifyCatalog != null)
\r
381 notifyCatalog.Changed += this.OnContainedCatalogChanged;
\r
382 notifyCatalog.Changing += this.OnContainedCatalogChanging;
\r
386 private void SubscribeToCatalogNotifications(IEnumerable<ComposablePartCatalog> catalogs)
\r
388 foreach (var catalog in catalogs)
\r
390 SubscribeToCatalogNotifications(catalog);
\r
394 private void UnsubscribeFromCatalogNotifications(ComposablePartCatalog catalog)
\r
396 INotifyComposablePartCatalogChanged notifyCatalog = catalog as INotifyComposablePartCatalogChanged;
\r
397 if (notifyCatalog != null)
\r
399 notifyCatalog.Changed -= this.OnContainedCatalogChanged;
\r
400 notifyCatalog.Changing -= this.OnContainedCatalogChanging;
\r
404 private void UnsubscribeFromCatalogNotifications(IEnumerable<ComposablePartCatalog> catalogs)
\r
406 foreach (var catalog in catalogs)
\r
408 UnsubscribeFromCatalogNotifications(catalog);
\r
412 private void ThrowIfDisposed()
\r
414 if (this._isDisposed)
\r
416 throw ExceptionBuilder.CreateObjectDisposed(this);
\r