/
CompositionContainer.cs
610 lines (547 loc) · 26.6 KB
/
CompositionContainer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
// 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);
}
}
}
}