-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathAssemblyContext.cs
More file actions
76 lines (69 loc) · 3.28 KB
/
AssemblyContext.cs
File metadata and controls
76 lines (69 loc) · 3.28 KB
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Cuemon.Collections.Generic;
namespace Cuemon.Reflection;
/// <summary>
/// Provides a set of static methods and properties to manage and filter assemblies in the current application domain.
/// </summary>
public static class AssemblyContext
{
/// <summary>
/// Gets the qualified assemblies from the current application domain.
/// </summary>
/// <param name="setup">The <see cref="AssemblyContextOptions"/> which may be configured.</param>
/// <returns>A read-only list of <see cref="Assembly"/> instances that match the filter criteria defined in <see cref="AssemblyContextOptions"/>.</returns>
/// <exception cref="ArgumentException">
/// <paramref name="setup"/> failed to configure an instance of <see cref="AssemblyContextOptions"/> in a valid state.
/// </exception>
public static IReadOnlyList<Assembly> GetCurrentDomainAssemblies(Action<AssemblyContextOptions> setup = null)
{
Validator.ThrowIfInvalidConfigurator(setup, out var options);
return AppDomain
.CurrentDomain
.GetAssemblies()
.Where(options.AssemblyFilter)
.SelectMany(options.IncludeReferencedAssemblies
? a => GetReferencedAssemblies(a, options.ReferencedAssemblyFilter)
: (Func<Assembly, IEnumerable<Assembly>>)(a => new[] { a }))
.Distinct()
.Except(options.ExcludedAssemblies)
.ToList()
.AsReadOnly();
}
/// <summary>
/// Recursively enumerates an <paramref name="assembly"/> and all of its referenced assemblies that satisfy the <paramref name="assemblyReferenceFilter"/>.
/// </summary>
/// <param name="assembly">The root <see cref="Assembly"/> from which to start traversal.</param>
/// <param name="assemblyReferenceFilter">A predicate used to filter which <see cref="AssemblyName"/> references are followed during traversal.</param>
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="Assembly"/> instances reachable from <paramref name="assembly"/> that pass the <paramref name="assemblyReferenceFilter"/>.</returns>
private static IEnumerable<Assembly> GetReferencedAssemblies(Assembly assembly, Func<AssemblyName, bool> assemblyReferenceFilter)
{
var stack = new Stack<Assembly>();
var guard = new HashSet<string>();
yield return assembly;
stack.Push(assembly);
guard.Add(assembly.FullName);
while (TryPop(stack, out var assemblyToTraverse))
{
foreach (var assemblyName in assemblyToTraverse.GetReferencedAssemblies().Where(assemblyReferenceFilter))
{
if (!guard.Add(assemblyName.FullName)) { continue; }
if (Patterns.TryInvoke(() => Assembly.Load(assemblyName), out var referencedAssembly) && referencedAssembly != null)
{
stack.Push(referencedAssembly);
yield return referencedAssembly;
}
}
}
}
private static bool TryPop(Stack<Assembly> stack, out Assembly assembly)
{
#if NET10_0_OR_GREATER
return stack.TryPop(out assembly);
#else
return Decorator.RawEnclose(stack).TryPop(out assembly);
#endif
}
}