diff --git a/ArchUnitNET/Domain/Architecture.cs b/ArchUnitNET/Domain/Architecture.cs
index 2e4cc0ddb..bbeb130ed 100644
--- a/ArchUnitNET/Domain/Architecture.cs
+++ b/ArchUnitNET/Domain/Architecture.cs
@@ -1,20 +1,45 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace ArchUnitNET.Domain
{
+ ///
+ /// Represents a loaded and analyzed software architecture, containing all assemblies,
+ /// namespaces, types, and their relationships.
+ ///
+ ///
+ /// An architecture is typically created via . Rule evaluation
+ /// results are cached by default to avoid recomputing the same provider multiple times.
+ /// Use to disable this caching.
+ ///
public class Architecture
{
private readonly IEnumerable _allAssemblies;
- private readonly ObjectProviderCache _objectProviderCache;
+ private readonly bool _useRuleEvaluationCache;
+ private readonly ConcurrentDictionary _ruleEvaluationCache =
+ new ConcurrentDictionary();
+ ///
+ /// Creates a new architecture from the given domain elements.
+ ///
+ /// All assemblies in the architecture, including referenced-only assemblies.
+ /// The namespaces containing the loaded types.
+ /// The types loaded from the assemblies.
+ /// Generic parameters found in the loaded types.
+ /// Types referenced but not directly loaded.
+ ///
+ /// If true (the default), rule evaluation results are cached by object provider
+ /// hash code and type. Pass false to disable caching.
+ ///
public Architecture(
IEnumerable allAssemblies,
IEnumerable namespaces,
IEnumerable types,
IEnumerable genericParameters,
- IEnumerable referencedTypes
+ IEnumerable referencedTypes,
+ bool useRuleEvaluationCache = true
)
{
_allAssemblies = allAssemblies;
@@ -22,7 +47,7 @@ IEnumerable referencedTypes
Types = types;
GenericParameters = genericParameters;
ReferencedTypes = referencedTypes;
- _objectProviderCache = new ObjectProviderCache(this);
+ _useRuleEvaluationCache = useRuleEvaluationCache;
}
public IEnumerable Assemblies =>
@@ -44,13 +69,28 @@ IEnumerable referencedTypes
public IEnumerable MethodMembers => Members.OfType();
public IEnumerable Members => Types.SelectMany(type => type.Members);
+ ///
+ /// Returns the cached result for the given object provider, or invokes the providing
+ /// function and caches the result. Caching can be disabled via
+ /// .
+ ///
public IEnumerable GetOrCreateObjects(
IObjectProvider objectProvider,
Func> providingFunction
)
where T : ICanBeAnalyzed
{
- return _objectProviderCache.GetOrCreateObjects(objectProvider, providingFunction);
+ if (!_useRuleEvaluationCache)
+ {
+ return providingFunction(this);
+ }
+ unchecked
+ {
+ var key =
+ (objectProvider.GetHashCode() * 397) ^ objectProvider.GetType().GetHashCode();
+ return (IEnumerable)
+ _ruleEvaluationCache.GetOrAdd(key, _ => providingFunction(this));
+ }
}
public override bool Equals(object obj)
diff --git a/ArchUnitNET/Domain/ArchitectureCache.cs b/ArchUnitNET/Domain/ArchitectureCache.cs
index 7e47e9a62..43c909e10 100644
--- a/ArchUnitNET/Domain/ArchitectureCache.cs
+++ b/ArchUnitNET/Domain/ArchitectureCache.cs
@@ -2,6 +2,19 @@
namespace ArchUnitNET.Domain
{
+ ///
+ /// A singleton cache that stores instances keyed by
+ /// . This avoids re-loading and re-analyzing
+ /// assemblies when the same set of assemblies is loaded multiple times (e.g., across
+ /// multiple test classes that share the same architecture).
+ ///
+ ///
+ /// The architecture cache operates at the assembly-loading level: it caches the fully
+ /// constructed object. This is separate from rule evaluation
+ /// caching, which caches individual rule evaluation results within an architecture.
+ /// Use on ArchLoader to bypass this
+ /// cache when building an architecture.
+ ///
public class ArchitectureCache
{
protected readonly ConcurrentDictionary Cache =
@@ -9,8 +22,16 @@ public class ArchitectureCache
protected ArchitectureCache() { }
+ ///
+ /// Gets the singleton instance of the architecture cache.
+ ///
public static ArchitectureCache Instance { get; } = new ArchitectureCache();
+ ///
+ /// Attempts to retrieve a cached architecture for the given key.
+ ///
+ /// The key identifying the architecture.
+ /// The cached architecture, or null if not found.
public Architecture TryGetArchitecture(ArchitectureCacheKey architectureCacheKey)
{
return Cache.TryGetValue(architectureCacheKey, out var matchArchitecture)
@@ -18,11 +39,20 @@ public Architecture TryGetArchitecture(ArchitectureCacheKey architectureCacheKey
: null;
}
+ ///
+ /// Adds an architecture to the cache. If the key already exists, the existing entry is kept.
+ ///
+ /// The key identifying the architecture.
+ /// The architecture to cache.
+ /// true if the architecture was added; false if the key already existed.
public bool Add(ArchitectureCacheKey architectureCacheKey, Architecture architecture)
{
return Cache.TryAdd(architectureCacheKey, architecture);
}
+ ///
+ /// Removes all entries from the cache.
+ ///
public void Clear() => Cache.Clear();
}
}
diff --git a/ArchUnitNET/Domain/ArchitectureCacheKey.cs b/ArchUnitNET/Domain/ArchitectureCacheKey.cs
index 3c075420a..510317033 100644
--- a/ArchUnitNET/Domain/ArchitectureCacheKey.cs
+++ b/ArchUnitNET/Domain/ArchitectureCacheKey.cs
@@ -5,22 +5,47 @@
namespace ArchUnitNET.Domain
{
+ ///
+ /// Identifies a cached by the set of loaded modules,
+ /// their namespace filters, and whether rule evaluation caching is disabled.
+ /// Two keys are equal when they represent the same combination of modules, filters,
+ /// and caching flag, regardless of insertion order.
+ ///
public class ArchitectureCacheKey : IEquatable
{
private readonly SortedSet<(string moduleName, string filter)> _architectureCacheKey =
new SortedSet<(string moduleName, string filter)>(new ArchitectureCacheKeyComparer());
+ private bool _ruleEvaluationCacheDisabled;
+
public bool Equals(ArchitectureCacheKey other)
{
return other != null
+ && _ruleEvaluationCacheDisabled == other._ruleEvaluationCacheDisabled
&& _architectureCacheKey.SequenceEqual(other._architectureCacheKey);
}
+ ///
+ /// Adds a module and optional namespace filter to this key.
+ ///
+ /// The name of the loaded module.
+ ///
+ /// The namespace filter applied when loading, or null if no filter was used.
+ ///
public void Add(string moduleName, string filter)
{
_architectureCacheKey.Add((moduleName, filter));
}
+ ///
+ /// Marks this key as representing an architecture with rule evaluation caching disabled.
+ /// Architectures with caching disabled are stored separately in the cache.
+ ///
+ public void SetRuleEvaluationCacheDisabled()
+ {
+ _ruleEvaluationCacheDisabled = true;
+ }
+
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
@@ -45,6 +70,7 @@ public override int GetHashCode()
{
hashCode = (hashCode * 131) ^ tuple.GetHashCode();
});
+ hashCode = (hashCode * 131) ^ _ruleEvaluationCacheDisabled.GetHashCode();
return hashCode;
}
}
diff --git a/ArchUnitNET/Domain/ObjectProviderCache.cs b/ArchUnitNET/Domain/ObjectProviderCache.cs
deleted file mode 100644
index 15722cae9..000000000
--- a/ArchUnitNET/Domain/ObjectProviderCache.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using ArchUnitNET.Fluent;
-
-namespace ArchUnitNET.Domain
-{
- public class ObjectProviderCache
- {
- private readonly Architecture _architecture;
- private readonly ConcurrentDictionary _cache;
-
- public ObjectProviderCache(Architecture architecture)
- {
- _architecture = architecture;
- _cache = new ConcurrentDictionary();
- }
-
- public IEnumerable GetOrCreateObjects(
- IObjectProvider objectProvider,
- Func> providingFunction
- )
- where T : ICanBeAnalyzed
- {
- unchecked
- {
- var key =
- (objectProvider.GetHashCode() * 397) ^ objectProvider.GetType().GetHashCode();
- return (IEnumerable)_cache.GetOrAdd(key, k => providingFunction(_architecture));
- }
- }
- }
-}
diff --git a/ArchUnitNET/Loader/ArchBuilder.cs b/ArchUnitNET/Loader/ArchBuilder.cs
index db9af6a0d..9df3c9df8 100644
--- a/ArchUnitNET/Loader/ArchBuilder.cs
+++ b/ArchUnitNET/Loader/ArchBuilder.cs
@@ -2,63 +2,88 @@
using System.Linq;
using ArchUnitNET.Domain;
using ArchUnitNET.Domain.Extensions;
-using ArchUnitNET.Loader.LoadTasks;
using JetBrains.Annotations;
using Mono.Cecil;
using GenericParameter = ArchUnitNET.Domain.GenericParameter;
namespace ArchUnitNET.Loader
{
+ ///
+ /// Internal builder that constructs an from loaded modules.
+ /// Coordinates type discovery (via ), type processing
+ /// (via ), and architecture assembly. Manages the
+ /// lookup and supports disabling rule-evaluation caching.
+ ///
internal class ArchBuilder
{
private readonly ArchitectureCache _architectureCache;
private readonly ArchitectureCacheKey _architectureCacheKey;
- private readonly IDictionary _architectureTypes =
- new Dictionary();
- private readonly AssemblyRegistry _assemblyRegistry;
- private readonly LoadTaskRegistry _loadTaskRegistry;
- private readonly NamespaceRegistry _namespaceRegistry;
- private readonly TypeFactory _typeFactory;
+ private readonly DomainResolver _domainResolver;
+
+ ///
+ /// Non-compiler-generated types paired with their Cecil s,
+ /// collected during and consumed by
+ /// to populate members, dependencies, and attributes.
+ /// Keyed by assembly-qualified name for deduplication.
+ ///
+ private readonly Dictionary<
+ string,
+ (ITypeInstance TypeInstance, TypeDefinition Definition)
+ > _typesToProcess =
+ new Dictionary<
+ string,
+ (ITypeInstance TypeInstance, TypeDefinition Definition)
+ >();
+
+ ///
+ /// Assemblies paired with their Cecil s,
+ /// registered via . Keyed by assembly full name
+ /// for deduplication. Consumed by Phase 5 (assembly attribute collection).
+ ///
+ private readonly Dictionary<
+ string,
+ (Assembly Assembly, AssemblyDefinition Definition)
+ > _assemblyData =
+ new Dictionary();
public ArchBuilder()
{
- _assemblyRegistry = new AssemblyRegistry();
- _namespaceRegistry = new NamespaceRegistry();
- _loadTaskRegistry = new LoadTaskRegistry();
- _typeFactory = new TypeFactory(
- _loadTaskRegistry,
- _assemblyRegistry,
- _namespaceRegistry
- );
+ _domainResolver = new DomainResolver();
_architectureCacheKey = new ArchitectureCacheKey();
_architectureCache = ArchitectureCache.Instance;
}
- public IEnumerable Types => _architectureTypes.Values;
- public IEnumerable Assemblies => _assemblyRegistry.Assemblies;
- public IEnumerable Namespaces => _namespaceRegistry.Namespaces;
-
+ ///
+ /// Registers an assembly for attribute collection during .
+ /// Skips assemblies that have already been registered.
+ ///
public void AddAssembly([NotNull] AssemblyDefinition moduleAssembly, bool isOnlyReferenced)
{
+ if (_assemblyData.ContainsKey(moduleAssembly.FullName))
+ {
+ return;
+ }
+
var references = moduleAssembly
.MainModule.AssemblyReferences.Select(reference => reference.Name)
.ToList();
- if (!_assemblyRegistry.ContainsAssembly(moduleAssembly.FullName))
- {
- var assembly = _assemblyRegistry.GetOrCreateAssembly(
- moduleAssembly.Name.Name,
- moduleAssembly.FullName,
- isOnlyReferenced,
- references
- );
- _loadTaskRegistry.Add(
- typeof(CollectAssemblyAttributes),
- new CollectAssemblyAttributes(assembly, moduleAssembly, _typeFactory)
- );
- }
+ var assembly = _domainResolver.GetOrCreateAssembly(
+ moduleAssembly.Name.Name,
+ moduleAssembly.FullName,
+ isOnlyReferenced,
+ references
+ );
+ _assemblyData.Add(moduleAssembly.FullName, (assembly, moduleAssembly));
}
+ ///
+ /// Discovers types in the given , creates domain type instances
+ /// via , and records them for later processing.
+ /// Filters out compiler-generated types, code-coverage instrumentation types,
+ /// nullable context attributes, and types outside the optional
+ /// .
+ ///
public void LoadTypesForModule(ModuleDefinition module, string namespaceFilter)
{
_architectureCacheKey.Add(module.Name, namespaceFilter);
@@ -87,10 +112,12 @@ public void LoadTypesForModule(ModuleDefinition module, string namespaceFilter)
types.AddRange(nestedTypes);
}
- var currentTypes = new List(types.Count);
types
.Where(typeDefinition =>
- RegexUtils.MatchNamespaces(namespaceFilter, typeDefinition.Namespace)
+ (
+ namespaceFilter == null
+ || typeDefinition.Namespace.StartsWith(namespaceFilter)
+ )
&& typeDefinition.CustomAttributes.All(att =>
att.AttributeType.FullName
!= "Microsoft.VisualStudio.TestPlatform.TestSDKAutoGeneratedCode"
@@ -98,68 +125,186 @@ public void LoadTypesForModule(ModuleDefinition module, string namespaceFilter)
)
.ForEach(typeDefinition =>
{
- var type = _typeFactory.GetOrCreateTypeFromTypeReference(typeDefinition);
+ var typeInstance = _domainResolver.GetOrCreateTypeInstanceFromTypeReference(
+ typeDefinition
+ );
+ var type = typeInstance.Type;
var assemblyQualifiedName = System.Reflection.Assembly.CreateQualifiedName(
module.Assembly.Name.Name,
typeDefinition.FullName
);
if (
- !_architectureTypes.ContainsKey(assemblyQualifiedName)
+ !_typesToProcess.ContainsKey(assemblyQualifiedName)
&& !type.IsCompilerGenerated
)
{
- currentTypes.Add(type);
- _architectureTypes.Add(assemblyQualifiedName, type);
+ _typesToProcess.Add(assemblyQualifiedName, (typeInstance, typeDefinition));
}
});
-
- _loadTaskRegistry.Add(
- typeof(AddTypesToNamespaces),
- new AddTypesToNamespaces(currentTypes)
- );
}
- private void UpdateTypeDefinitions()
+ ///
+ /// Builds the from all loaded modules. Returns a cached
+ /// instance when available (unless is set).
+ ///
+ public Architecture Build(bool skipRuleEvaluationCache, bool skipArchitectureCache)
{
- _loadTaskRegistry.ExecuteTasks(
- new List
- {
- typeof(AddMembers),
- typeof(AddGenericParameterDependencies),
- typeof(AddAttributesAndAttributeDependencies),
- typeof(CollectAssemblyAttributes),
- typeof(AddFieldAndPropertyDependencies),
- typeof(AddMethodDependencies),
- typeof(AddGenericArgumentDependencies),
- typeof(AddClassDependencies),
- typeof(AddBackwardsDependencies),
- typeof(AddTypesToNamespaces),
- }
- );
- }
+ if (skipRuleEvaluationCache)
+ {
+ _architectureCacheKey.SetRuleEvaluationCacheDisabled();
+ }
- public Architecture Build()
- {
- var architecture = _architectureCache.TryGetArchitecture(_architectureCacheKey);
- if (architecture != null)
+ if (!skipArchitectureCache)
{
- return architecture;
+ var architecture = _architectureCache.TryGetArchitecture(_architectureCacheKey);
+ if (architecture != null)
+ {
+ return architecture;
+ }
}
- UpdateTypeDefinitions();
- var allTypes = _typeFactory.GetAllNonCompilerGeneratedTypes().ToList();
+ ProcessTypes();
+
+ var allTypes = _domainResolver
+ .Types.Select(instance => instance.Type)
+ .Where(type => !type.IsCompilerGenerated)
+ .Distinct()
+ .ToList();
+ var types = allTypes
+ .Where(type => !type.IsStub && !(type is GenericParameter))
+ .ToList();
var genericParameters = allTypes.OfType().ToList();
- var referencedTypes = allTypes.Except(Types).Except(genericParameters);
- var namespaces = Namespaces.Where(ns => ns.Types.Any());
+ var referencedTypes = allTypes
+ .Where(type => type.IsStub && !(type is GenericParameter))
+ .ToList();
+ var namespaces = _domainResolver.Namespaces.Where(ns => ns.Types.Any());
var newArchitecture = new Architecture(
- Assemblies,
+ _domainResolver.Assemblies,
namespaces,
- Types,
+ types,
genericParameters,
- referencedTypes
+ referencedTypes,
+ !skipRuleEvaluationCache
);
- _architectureCache.Add(_architectureCacheKey, newArchitecture);
+
+ if (!skipArchitectureCache)
+ {
+ _architectureCache.Add(_architectureCacheKey, newArchitecture);
+ }
+
return newArchitecture;
}
+
+ ///
+ /// Runs all type-processing phases in the required order across every discovered type.
+ /// Each phase must complete for all types before the next phase begins, because later
+ /// phases depend on data populated by earlier ones (e.g. members must exist before
+ /// method dependencies can be resolved).
+ ///
+ private void ProcessTypes()
+ {
+ var typesToProcess = _typesToProcess.Values;
+
+ // Phase 1: Base class dependency (non-interface types only)
+ foreach (var entry in typesToProcess.Where(entry => !entry.Definition.IsInterface))
+ {
+ TypeProcessor.AddBaseClassDependency(
+ entry.TypeInstance.Type,
+ entry.Definition,
+ _domainResolver
+ );
+ }
+
+ // Phase 2: Members (fields, properties, methods)
+ // Collect (TypeInstance, Definition, MemberData) for use in Phases 4 and 7.
+ var typesWithMemberData = new List<(
+ ITypeInstance TypeInstance,
+ TypeDefinition TypeDef,
+ MemberData MemberData
+ )>(_typesToProcess.Count);
+ typesWithMemberData.AddRange(
+ from entry in typesToProcess
+ let memberData = TypeProcessor.AddMembers(
+ entry.TypeInstance,
+ entry.Definition,
+ _domainResolver
+ )
+ select (entry.TypeInstance, entry.Definition, memberData)
+ );
+
+ // Phase 3: Generic parameter dependencies
+ foreach (var entry in typesToProcess)
+ {
+ TypeProcessor.AddGenericParameterDependencies(entry.TypeInstance.Type);
+ }
+
+ // Phase 4: Attributes and attribute dependencies
+ foreach (var entry in typesWithMemberData)
+ {
+ TypeProcessor.AddAttributesAndAttributeDependencies(
+ entry.TypeInstance.Type,
+ entry.TypeDef,
+ _domainResolver,
+ entry.MemberData.MethodPairs
+ );
+ }
+
+ // Phase 5: Assembly-level attributes
+ // Materialized to a list because CollectAssemblyAttributes can trigger
+ // GetOrCreateAssembly in DomainResolver, which would invalidate a lazy query.
+ var assemblyData = _assemblyData.Values.ToList();
+ foreach (var entry in assemblyData)
+ {
+ TypeProcessor.CollectAssemblyAttributes(
+ entry.Assembly,
+ entry.Definition,
+ _domainResolver
+ );
+ }
+
+ // Phase 6: Field and property type dependencies
+ foreach (var entry in typesToProcess)
+ {
+ TypeProcessor.AddFieldAndPropertyDependencies(entry.TypeInstance.Type);
+ }
+
+ // Phase 7: Method signature and body dependencies
+ foreach (var entry in typesWithMemberData)
+ {
+ TypeProcessor.AddMethodDependencies(
+ entry.TypeInstance.Type,
+ _domainResolver,
+ entry.MemberData.MethodPairs,
+ entry.MemberData.PropertyByAccessor
+ );
+ }
+
+ // Phase 8: Generic argument dependencies
+ foreach (var entry in typesToProcess)
+ {
+ TypeProcessor.AddGenericArgumentDependencies(entry.TypeInstance.Type);
+ }
+
+ // Phase 9: Interface and member-to-type dependencies
+ foreach (var entry in typesToProcess)
+ {
+ TypeProcessor.AddClassDependencies(
+ entry.TypeInstance.Type,
+ entry.Definition,
+ _domainResolver
+ );
+ }
+
+ // Phase 10: Backwards dependencies
+ foreach (var entry in typesToProcess)
+ {
+ TypeProcessor.AddBackwardsDependencies(entry.TypeInstance.Type);
+ }
+
+ // Phase 11: Register types with their namespaces
+ TypeProcessor.AddTypesToNamespaces(
+ _typesToProcess.Values.Select(entry => entry.TypeInstance.Type)
+ );
+ }
}
}
diff --git a/ArchUnitNET/Loader/ArchLoader.cs b/ArchUnitNET/Loader/ArchLoader.cs
index 5ac7ab920..be28272df 100644
--- a/ArchUnitNET/Loader/ArchLoader.cs
+++ b/ArchUnitNET/Loader/ArchLoader.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -12,106 +12,279 @@ namespace ArchUnitNET.Loader
{
public class ArchLoader
{
- private readonly ArchBuilder _archBuilder = new ArchBuilder();
- private DotNetCoreAssemblyResolver _assemblyResolver = new DotNetCoreAssemblyResolver();
+ private bool _skipRuleEvaluationCache;
+ private bool _skipArchitectureCache;
+ private readonly List _loadInstructions = new List();
- public Architecture Build()
+ // ---------------------------------------------------------------------------
+ // LoadInstruction hierarchy — replaces the old tagged-union struct
+ // ---------------------------------------------------------------------------
+
+ private abstract class LoadInstruction
{
- var architecture = _archBuilder.Build();
- _assemblyResolver.Dispose();
- _assemblyResolver = new DotNetCoreAssemblyResolver();
+ public bool IncludeDependencies { get; }
+ public bool Recursive { get; }
- return architecture;
+ protected LoadInstruction(bool includeDependencies, bool recursive)
+ {
+ IncludeDependencies = includeDependencies;
+ Recursive = recursive;
+ }
}
- public ArchLoader LoadAssemblies(params Assembly[] assemblies)
+ private sealed class FileLoadInstruction : LoadInstruction
{
- var assemblySet = new HashSet(assemblies);
- assemblySet.ForEach(assembly => LoadAssembly(assembly));
- return this;
+ public string FileName { get; }
+ public string NamespaceFilter { get; }
+ public FilterFunc FilterFunc { get; }
+
+ public FileLoadInstruction(
+ string fileName,
+ string namespaceFilter,
+ bool includeDependencies,
+ bool recursive,
+ FilterFunc filterFunc = null
+ )
+ : base(includeDependencies, recursive)
+ {
+ FileName = fileName;
+ NamespaceFilter = namespaceFilter;
+ FilterFunc = filterFunc;
+ }
}
- public ArchLoader LoadAssembliesIncludingDependencies(params Assembly[] assemblies)
+ private sealed class DirectoryLoadInstruction : LoadInstruction
{
- return LoadAssembliesIncludingDependencies(assemblies, false);
+ public string Directory { get; }
+ public string DirectoryFilter { get; }
+ public SearchOption SearchOption { get; }
+
+ public DirectoryLoadInstruction(
+ string directory,
+ string directoryFilter,
+ SearchOption searchOption,
+ bool includeDependencies,
+ bool recursive
+ )
+ : base(includeDependencies, recursive)
+ {
+ Directory = directory;
+ DirectoryFilter = directoryFilter;
+ SearchOption = searchOption;
+ }
}
- public ArchLoader LoadAssembliesIncludingDependencies(
- IEnumerable assemblies,
- bool recursive
- )
+ // ---------------------------------------------------------------------------
+ // Cache configuration
+ // ---------------------------------------------------------------------------
+
+ ///
+ /// Configures this loader to disable rule evaluation caching in the built architecture.
+ /// Each call to will invoke the providing
+ /// function directly instead of returning a cached result.
+ ///
+ /// This loader instance for method chaining.
+ public ArchLoader WithoutRuleEvaluationCache()
{
- var assemblySet = new HashSet(assemblies);
- assemblySet.ForEach(assembly => LoadAssemblyIncludingDependencies(assembly, recursive));
+ _skipRuleEvaluationCache = true;
return this;
}
- public ArchLoader LoadFilteredDirectory(
- string directory,
- string filter,
- SearchOption searchOption = TopDirectoryOnly
- )
+ ///
+ /// Configures this loader to skip the global when
+ /// building the architecture. Each call to will create a fresh
+ /// architecture instance instead of returning a cached one.
+ ///
+ /// This loader instance for method chaining.
+ public ArchLoader WithoutArchitectureCache()
{
- var path = Path.GetFullPath(directory);
- _assemblyResolver.AssemblyPath = path;
- var assemblies = Directory.GetFiles(path, filter, searchOption);
+ _skipArchitectureCache = true;
+ return this;
+ }
- var result = this;
- return assemblies.Aggregate(
- result,
- (current, assembly) => current.LoadAssembly(assembly, false, false)
- );
+ // ---------------------------------------------------------------------------
+ // Build
+ // ---------------------------------------------------------------------------
+
+ public Architecture Build()
+ {
+ var assemblyResolver = new DotNetCoreAssemblyResolver();
+ try
+ {
+ var archBuilder = new ArchBuilder();
+ foreach (var instruction in _loadInstructions)
+ {
+ if (instruction is DirectoryLoadInstruction dir)
+ {
+ ProcessInstruction(assemblyResolver, archBuilder, dir);
+ }
+ else
+ {
+ ProcessInstruction(
+ assemblyResolver,
+ archBuilder,
+ (FileLoadInstruction)instruction
+ );
+ }
+ }
+
+ return archBuilder.Build(_skipRuleEvaluationCache, _skipArchitectureCache);
+ }
+ finally
+ {
+ assemblyResolver.Dispose();
+ }
}
- public ArchLoader LoadFilteredDirectoryIncludingDependencies(
- string directory,
- string filter,
- bool recursive = false,
- SearchOption searchOption = TopDirectoryOnly
+ // ---------------------------------------------------------------------------
+ // Instruction processing
+ // ---------------------------------------------------------------------------
+
+ private static void ProcessInstruction(
+ DotNetCoreAssemblyResolver assemblyResolver,
+ ArchBuilder archBuilder,
+ DirectoryLoadInstruction instruction
)
{
- var path = Path.GetFullPath(directory);
- _assemblyResolver.AssemblyPath = path;
- var assemblies = Directory.GetFiles(path, filter, searchOption);
+ var path = Path.GetFullPath(instruction.Directory);
+ assemblyResolver.AssemblyPath = path;
+ var files = System.IO.Directory.GetFiles(
+ path,
+ instruction.DirectoryFilter,
+ instruction.SearchOption
+ );
+ foreach (var file in files)
+ {
+ LoadModule(
+ assemblyResolver,
+ archBuilder,
+ file,
+ null,
+ instruction.IncludeDependencies,
+ instruction.Recursive
+ );
+ }
+ }
- var result = this;
- return assemblies.Aggregate(
- result,
- (current, assembly) => current.LoadAssembly(assembly, true, recursive)
+ private static void ProcessInstruction(
+ DotNetCoreAssemblyResolver assemblyResolver,
+ ArchBuilder archBuilder,
+ FileLoadInstruction instruction
+ )
+ {
+ LoadModule(
+ assemblyResolver,
+ archBuilder,
+ instruction.FileName,
+ instruction.NamespaceFilter,
+ instruction.IncludeDependencies,
+ instruction.Recursive,
+ instruction.FilterFunc
);
}
- public ArchLoader LoadNamespacesWithinAssembly(Assembly assembly, params string[] namespc)
+ // ---------------------------------------------------------------------------
+ // Public loading API
+ // ---------------------------------------------------------------------------
+
+ public ArchLoader LoadAssemblies(params Assembly[] assemblies)
{
- var nameSpaces = new HashSet(namespc);
- nameSpaces.ForEach(nameSpace =>
+ foreach (var assembly in new HashSet(assemblies))
{
- LoadModule(assembly.Location, nameSpace, false, false);
- });
+ _loadInstructions.Add(
+ new FileLoadInstruction(assembly.Location, null, false, false)
+ );
+ }
return this;
}
- public ArchLoader LoadAssembly(Assembly assembly)
+ public ArchLoader LoadAssembly(Assembly assembly) => LoadAssemblies(assembly);
+
+ public ArchLoader LoadAssembliesIncludingDependencies(params Assembly[] assemblies) =>
+ LoadAssembliesIncludingDependencies(assemblies, false);
+
+ public ArchLoader LoadAssembliesIncludingDependencies(
+ IEnumerable assemblies,
+ bool recursive
+ )
{
- return LoadAssembly(assembly.Location, false, false);
+ foreach (var assembly in new HashSet(assemblies))
+ {
+ _loadInstructions.Add(
+ new FileLoadInstruction(assembly.Location, null, true, recursive)
+ );
+ }
+ return this;
}
public ArchLoader LoadAssemblyIncludingDependencies(
Assembly assembly,
bool recursive = false
+ ) => LoadAssembliesIncludingDependencies(new[] { assembly }, recursive);
+
+ public ArchLoader LoadNamespacesWithinAssembly(Assembly assembly, params string[] namespc)
+ {
+ foreach (var nameSpace in new HashSet(namespc))
+ {
+ _loadInstructions.Add(
+ new FileLoadInstruction(assembly.Location, nameSpace, false, false)
+ );
+ }
+ return this;
+ }
+
+ ///
+ /// Loads assemblies from dependency tree with user-defined filtration logic
+ ///
+ /// Assemblies to start traversal from
+ /// Delegate to control loading and traversal logic
+ ///
+ public ArchLoader LoadAssembliesRecursively(
+ IEnumerable assemblies,
+ FilterFunc filterFunc
)
{
- return LoadAssembly(assembly.Location, true, recursive);
+ foreach (var assembly in assemblies)
+ {
+ _loadInstructions.Add(
+ new FileLoadInstruction(assembly.Location, null, true, true, filterFunc)
+ );
+ }
+ return this;
}
- private ArchLoader LoadAssembly(string fileName, bool includeDependencies, bool recursive)
+ public ArchLoader LoadFilteredDirectory(
+ string directory,
+ string filter,
+ SearchOption searchOption = TopDirectoryOnly
+ )
{
- LoadModule(fileName, null, includeDependencies, recursive);
+ _loadInstructions.Add(
+ new DirectoryLoadInstruction(directory, filter, searchOption, false, false)
+ );
+ return this;
+ }
+ public ArchLoader LoadFilteredDirectoryIncludingDependencies(
+ string directory,
+ string filter,
+ bool recursive = false,
+ SearchOption searchOption = TopDirectoryOnly
+ )
+ {
+ _loadInstructions.Add(
+ new DirectoryLoadInstruction(directory, filter, searchOption, true, recursive)
+ );
return this;
}
- private void LoadModule(
+ // ---------------------------------------------------------------------------
+ // Core module loading
+ // ---------------------------------------------------------------------------
+
+ private static void LoadModule(
+ DotNetCoreAssemblyResolver assemblyResolver,
+ ArchBuilder archBuilder,
string fileName,
string nameSpace,
bool includeDependencies,
@@ -123,17 +296,19 @@ private void LoadModule(
{
var module = ModuleDefinition.ReadModule(
fileName,
- new ReaderParameters { AssemblyResolver = _assemblyResolver }
+ new ReaderParameters { AssemblyResolver = assemblyResolver }
);
- var processedAssemblies = new List { module.Assembly.Name };
+ var processedAssemblies = new HashSet { module.Assembly.Name.FullName };
var resolvedModules = new List();
- _assemblyResolver.AddLib(module.Assembly);
- _archBuilder.AddAssembly(module.Assembly, false);
+ assemblyResolver.AddLib(module.Assembly);
+ archBuilder.AddAssembly(module.Assembly, false);
foreach (var assemblyReference in module.AssemblyReferences)
{
if (includeDependencies && recursive)
{
AddReferencedAssembliesRecursively(
+ assemblyResolver,
+ archBuilder,
assemblyReference,
processedAssemblies,
resolvedModules,
@@ -144,14 +319,14 @@ private void LoadModule(
{
try
{
- processedAssemblies.Add(assemblyReference);
- _assemblyResolver.AddLib(assemblyReference);
+ processedAssemblies.Add(assemblyReference.FullName);
+ assemblyResolver.AddLib(assemblyReference);
if (includeDependencies)
{
var assemblyDefinition =
- _assemblyResolver.Resolve(assemblyReference)
+ assemblyResolver.Resolve(assemblyReference)
?? throw new AssemblyResolutionException(assemblyReference);
- _archBuilder.AddAssembly(assemblyDefinition, false);
+ archBuilder.AddAssembly(assemblyDefinition, false);
resolvedModules.AddRange(assemblyDefinition.Modules);
}
}
@@ -162,10 +337,10 @@ private void LoadModule(
}
}
- _archBuilder.LoadTypesForModule(module, nameSpace);
+ archBuilder.LoadTypesForModule(module, nameSpace);
foreach (var moduleDefinition in resolvedModules)
{
- _archBuilder.LoadTypesForModule(moduleDefinition, null);
+ archBuilder.LoadTypesForModule(moduleDefinition, null);
}
}
catch (BadImageFormatException)
@@ -174,30 +349,32 @@ private void LoadModule(
}
}
- private void AddReferencedAssembliesRecursively(
+ private static void AddReferencedAssembliesRecursively(
+ DotNetCoreAssemblyResolver assemblyResolver,
+ ArchBuilder archBuilder,
AssemblyNameReference currentAssemblyReference,
- ICollection processedAssemblies,
+ ICollection processedAssemblies,
List resolvedModules,
FilterFunc filterFunc
)
{
- if (processedAssemblies.Contains(currentAssemblyReference))
+ if (processedAssemblies.Contains(currentAssemblyReference.FullName))
{
return;
}
- processedAssemblies.Add(currentAssemblyReference);
+ processedAssemblies.Add(currentAssemblyReference.FullName);
try
{
- _assemblyResolver.AddLib(currentAssemblyReference);
+ assemblyResolver.AddLib(currentAssemblyReference);
var assemblyDefinition =
- _assemblyResolver.Resolve(currentAssemblyReference)
+ assemblyResolver.Resolve(currentAssemblyReference)
?? throw new AssemblyResolutionException(currentAssemblyReference);
var filterResult = filterFunc?.Invoke(assemblyDefinition);
if (filterResult?.LoadThisAssembly != false)
{
- _archBuilder.AddAssembly(assemblyDefinition, false);
+ archBuilder.AddAssembly(assemblyDefinition, false);
resolvedModules.AddRange(assemblyDefinition.Modules);
}
@@ -209,6 +386,8 @@ var reference in assemblyDefinition.Modules.SelectMany(m =>
{
if (filterResult?.TraverseDependencies != false)
AddReferencedAssembliesRecursively(
+ assemblyResolver,
+ archBuilder,
reference,
processedAssemblies,
resolvedModules,
@@ -221,23 +400,5 @@ var reference in assemblyDefinition.Modules.SelectMany(m =>
//Failed to resolve assembly, skip it
}
}
-
- ///
- /// Loads assemblies from dependency tree with user-defined filtration logic
- ///
- /// Assemblies to start traversal from
- /// Delegate to control loading and traversal logic
- ///
- public ArchLoader LoadAssembliesRecursively(
- IEnumerable assemblies,
- FilterFunc filterFunc
- )
- {
- foreach (var assembly in assemblies)
- {
- LoadModule(assembly.Location, null, true, true, filterFunc);
- }
- return this;
- }
}
}
diff --git a/ArchUnitNET/Loader/AssemblyRegistry.cs b/ArchUnitNET/Loader/AssemblyRegistry.cs
deleted file mode 100644
index 4b7469301..000000000
--- a/ArchUnitNET/Loader/AssemblyRegistry.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System.Collections.Generic;
-using ArchUnitNET.Domain;
-
-namespace ArchUnitNET.Loader
-{
- internal class AssemblyRegistry
- {
- private readonly Dictionary _assemblies =
- new Dictionary();
-
- public IEnumerable Assemblies => _assemblies.Values;
-
- public Assembly GetOrCreateAssembly(
- string assemblyName,
- string assemblyFullName,
- bool isOnlyReferenced,
- List assemblyReferences
- )
- {
- return RegistryUtils.GetFromDictOrCreateAndAdd(
- assemblyFullName,
- _assemblies,
- s => new Assembly(
- assemblyName,
- assemblyFullName,
- isOnlyReferenced,
- assemblyReferences
- )
- );
- }
-
- public bool ContainsAssembly(string assemblyName)
- {
- return _assemblies.ContainsKey(assemblyName);
- }
- }
-}
diff --git a/ArchUnitNET/Loader/TypeFactory.cs b/ArchUnitNET/Loader/DomainResolver.cs
similarity index 75%
rename from ArchUnitNET/Loader/TypeFactory.cs
rename to ArchUnitNET/Loader/DomainResolver.cs
index 240598597..287d747ae 100644
--- a/ArchUnitNET/Loader/TypeFactory.cs
+++ b/ArchUnitNET/Loader/DomainResolver.cs
@@ -3,7 +3,6 @@
using System.Linq;
using System.Runtime.CompilerServices;
using ArchUnitNET.Domain;
-using ArchUnitNET.Loader.LoadTasks;
using JetBrains.Annotations;
using Mono.Cecil;
using static ArchUnitNET.Domain.Visibility;
@@ -13,11 +12,20 @@
namespace ArchUnitNET.Loader
{
- internal class TypeFactory
+ ///
+ /// Resolves Mono.Cecil type, method, and field references into cached domain objects.
+ /// Creates and deduplicates , ,
+ /// , , and
+ /// instances. Passed into every load-task phase as the single source of truth for
+ /// domain object resolution.
+ ///
+ internal class DomainResolver
{
- private readonly AssemblyRegistry _assemblyRegistry;
- private readonly LoadTaskRegistry _loadTaskRegistry;
- private readonly NamespaceRegistry _namespaceRegistry;
+ private readonly Dictionary _assemblies =
+ new Dictionary();
+
+ private readonly Dictionary _namespaces =
+ new Dictionary();
private readonly Dictionary> _allTypes =
new Dictionary>();
@@ -25,31 +33,83 @@ internal class TypeFactory
private readonly Dictionary _allMethods =
new Dictionary();
- public TypeFactory(
- LoadTaskRegistry loadTaskRegistry,
- AssemblyRegistry assemblyRegistry,
- NamespaceRegistry namespaceRegistry
+ private readonly Dictionary _allFields =
+ new Dictionary();
+
+ ///
+ /// All assemblies that have been created or cached.
+ ///
+ public IEnumerable Assemblies => _assemblies.Values;
+
+ ///
+ /// All namespaces that have been created or cached.
+ ///
+ public IEnumerable Namespaces => _namespaces.Values;
+
+ ///
+ /// All types that have been created or cached.
+ ///
+ public IEnumerable> Types => _allTypes.Values;
+
+ ///
+ /// Returns the existing for the given full name, or creates
+ /// and caches a new one.
+ ///
+ internal Assembly GetOrCreateAssembly(
+ string assemblyName,
+ string assemblyFullName,
+ bool isOnlyReferenced,
+ List assemblyReferences
)
{
- _loadTaskRegistry = loadTaskRegistry;
- _assemblyRegistry = assemblyRegistry;
- _namespaceRegistry = namespaceRegistry;
+ if (_assemblies.TryGetValue(assemblyFullName, out var existing))
+ {
+ return existing;
+ }
+
+ var assembly = new Assembly(
+ assemblyName,
+ assemblyFullName,
+ isOnlyReferenced,
+ assemblyReferences
+ );
+ _assemblies.Add(assemblyFullName, assembly);
+ return assembly;
}
- public IEnumerable GetAllNonCompilerGeneratedTypes()
+ ///
+ /// Returns the existing for the given name, or creates
+ /// and caches a new one.
+ ///
+ internal Namespace GetOrCreateNamespace(string namespaceName)
{
- return _allTypes
- .Values.Select(instance => instance.Type)
- .Distinct()
- .Where(type => !type.IsCompilerGenerated);
+ if (_namespaces.TryGetValue(namespaceName, out var existing))
+ {
+ return existing;
+ }
+
+ var ns = new Namespace(namespaceName, new List());
+ _namespaces.Add(namespaceName, ns);
+ return ns;
}
+ ///
+ /// Returns (or creates and caches) a type instance for the given type reference.
+ /// Used during type discovery in .
+ ///
[NotNull]
- internal IType GetOrCreateTypeFromTypeReference(TypeReference typeReference)
+ internal ITypeInstance GetOrCreateTypeInstanceFromTypeReference(
+ TypeReference typeReference
+ )
{
- return GetOrCreateTypeInstanceFromTypeReference(typeReference, false).Type;
+ return GetOrCreateTypeInstanceFromTypeReference(typeReference, false);
}
+ ///
+ /// Returns (or creates and caches) a stub type instance for the given type reference.
+ /// Used by load task phases when resolving dependency targets that may not have
+ /// been discovered during module loading.
+ ///
[NotNull]
internal ITypeInstance GetOrCreateStubTypeInstanceFromTypeReference(
TypeReference typeReference
@@ -96,23 +156,10 @@ bool isStub
return GetOrCreateTypeInstanceFromTypeDefinition(typeDefinition, isStub);
}
- var resolvedTypeDefinition = ResolveTypeReferenceToTypeDefinition(typeReference);
- if (resolvedTypeDefinition == null)
- {
- // When assemblies are loaded by path, there are cases where a dependent type cannot be resolved because
- // the assembly dependency is not loaded in the current application domain. In this case, we create a
- // stub type.
- return GetOrCreateUnavailableTypeFromTypeReference(typeReference);
- }
- return GetOrCreateTypeInstanceFromTypeDefinition(resolvedTypeDefinition, isStub);
- }
-
- private TypeDefinition ResolveTypeReferenceToTypeDefinition(TypeReference typeReference)
- {
- TypeDefinition typeDefinition;
+ TypeDefinition resolvedTypeDefinition;
try
{
- typeDefinition = typeReference.Resolve();
+ resolvedTypeDefinition = typeReference.Resolve();
}
catch (AssemblyResolutionException e)
{
@@ -121,7 +168,15 @@ private TypeDefinition ResolveTypeReferenceToTypeDefinition(TypeReference typeRe
e
);
}
- return typeDefinition;
+
+ if (resolvedTypeDefinition == null)
+ {
+ // When assemblies are loaded by path, there are cases where a dependent type cannot be resolved because
+ // the assembly dependency is not loaded in the current application domain. In this case, we create a
+ // stub type.
+ return GetOrCreateUnavailableTypeFromTypeReference(typeReference);
+ }
+ return GetOrCreateTypeInstanceFromTypeDefinition(resolvedTypeDefinition, isStub);
}
private ITypeInstance GetOrCreateGenericParameterTypeInstanceFromTypeReference(
@@ -263,10 +318,8 @@ bool isStub
{
declaringTypeReference = declaringTypeReference.DeclaringType;
}
- var currentNamespace = _namespaceRegistry.GetOrCreateNamespace(
- declaringTypeReference.Namespace
- );
- var currentAssembly = _assemblyRegistry.GetOrCreateAssembly(
+ var currentNamespace = GetOrCreateNamespace(declaringTypeReference.Namespace);
+ var currentAssembly = GetOrCreateAssembly(
assemblyFullName,
assemblyFullName,
true,
@@ -323,16 +376,6 @@ bool isStub
);
}
- if (!isStub && !isCompilerGenerated)
- {
- if (!typeDefinition.IsInterface)
- {
- LoadBaseTask(createdTypeInstance.Type, type, typeDefinition);
- }
-
- LoadNonBaseTasks(createdTypeInstance, type, typeDefinition);
- }
-
_allTypes.Add(assemblyQualifiedName, createdTypeInstance);
var genericParameters = GetGenericParameters(typeDefinition);
@@ -346,9 +389,10 @@ private ITypeInstance GetOrCreateUnavailableTypeFromTypeReferen
TypeReference typeReference
)
{
+ var typeReferenceFullName = typeReference.BuildFullName();
var assemblyQualifiedName = System.Reflection.Assembly.CreateQualifiedName(
typeReference.Scope.Name,
- typeReference.BuildFullName()
+ typeReferenceFullName
);
if (_allTypes.TryGetValue(assemblyQualifiedName, out var existingTypeInstance))
{
@@ -358,15 +402,15 @@ TypeReference typeReference
var result = new TypeInstance(
new UnavailableType(
new Type(
- typeReference.BuildFullName(),
+ typeReferenceFullName,
typeReference.Name,
- _assemblyRegistry.GetOrCreateAssembly(
+ GetOrCreateAssembly(
typeReference.Scope.Name,
typeReference.Scope.ToString(),
true,
null
),
- _namespaceRegistry.GetOrCreateNamespace(typeReference.Namespace),
+ GetOrCreateNamespace(typeReference.Namespace),
NotAccessible,
typeReference.IsNested,
typeReference.HasGenericParameters,
@@ -432,7 +476,7 @@ FunctionPointerType functionPointerType
Public,
false,
false,
- false,
+ true,
false
);
var returnTypeInstance = GetOrCreateStubTypeInstanceFromTypeReference(
@@ -450,6 +494,10 @@ FunctionPointerType functionPointerType
return result;
}
+ ///
+ /// Returns (or creates and caches) a for the
+ /// given method reference. Handles both generic and non-generic method references.
+ ///
[NotNull]
public MethodMemberInstance GetOrCreateMethodMemberFromMethodReference(
[NotNull] ITypeInstance typeInstance,
@@ -459,15 +507,22 @@ [NotNull] MethodReference methodReference
var methodReferenceFullName = methodReference.BuildFullName();
if (methodReference.IsGenericInstance)
{
- return RegistryUtils.GetFromDictOrCreateAndAdd(
- methodReferenceFullName,
- _allMethods,
- _ =>
- CreateGenericInstanceMethodMemberFromMethodReference(
- typeInstance,
- methodReference
- )
+ if (
+ _allMethods.TryGetValue(
+ methodReferenceFullName,
+ out var existingGenericInstance
+ )
+ )
+ {
+ return existingGenericInstance;
+ }
+
+ var genericResult = CreateGenericInstanceMethodMemberFromMethodReference(
+ typeInstance,
+ methodReference
);
+ _allMethods.Add(methodReferenceFullName, genericResult);
+ return genericResult;
}
if (_allMethods.TryGetValue(methodReferenceFullName, out var existingMethodInstance))
@@ -476,7 +531,7 @@ [NotNull] MethodReference methodReference
}
var name = methodReference.BuildMethodMemberName();
- var fullName = methodReference.BuildFullName();
+ var fullName = methodReferenceFullName;
var isGeneric = methodReference.HasGenericParameters;
var isCompilerGenerated = methodReference.IsCompilerGenerated();
MethodForm methodForm;
@@ -572,36 +627,102 @@ MethodReference methodReference
);
}
+ ///
+ /// Returns (or creates and caches) a for the given field
+ /// reference. When the reference is a , the field is
+ /// created with full fidelity (visibility, exact writability). Otherwise a stub with
+ /// default visibility () is created.
+ ///
[NotNull]
- internal FieldMember CreateStubFieldMemberFromFieldReference(
+ internal FieldMember GetOrCreateFieldMember(
[NotNull] IType type,
[NotNull] FieldReference fieldReference
)
{
+ var fullName = fieldReference.FullName;
+ if (_allFields.TryGetValue(fullName, out var existing))
+ {
+ return existing;
+ }
+
var typeReference = fieldReference.FieldType;
var fieldType = GetOrCreateStubTypeInstanceFromTypeReference(typeReference);
var isCompilerGenerated = fieldReference.IsCompilerGenerated();
- bool? isStatic = null;
- var isReadOnly = false;
+ Visibility visibility;
+ bool? isStatic;
+ Writability writeAccessor;
if (fieldReference is FieldDefinition fieldDefinition)
{
+ visibility = GetVisibilityFromFieldDefinition(fieldDefinition);
isStatic = fieldDefinition.IsStatic;
- isReadOnly = fieldDefinition.IsInitOnly;
+ writeAccessor = fieldDefinition.IsInitOnly
+ ? Writability.ReadOnly
+ : Writability.Writable;
+ }
+ else
+ {
+ visibility = Public;
+ isStatic = null;
+ writeAccessor = Writability.Writable;
}
- return new FieldMember(
+ var fieldMember = new FieldMember(
type,
fieldReference.Name,
- fieldReference.FullName,
- Public,
+ fullName,
+ visibility,
fieldType,
isCompilerGenerated,
isStatic,
- isReadOnly ? Writability.ReadOnly : Writability.Writable
+ writeAccessor
);
+
+ _allFields.Add(fullName, fieldMember);
+ return fieldMember;
}
+ private static Visibility GetVisibilityFromFieldDefinition(
+ [NotNull] FieldDefinition fieldDefinition
+ )
+ {
+ if (fieldDefinition.IsPublic)
+ {
+ return Public;
+ }
+
+ if (fieldDefinition.IsPrivate)
+ {
+ return Private;
+ }
+
+ if (fieldDefinition.IsFamily)
+ {
+ return Protected;
+ }
+
+ if (fieldDefinition.IsAssembly)
+ {
+ return Internal;
+ }
+
+ if (fieldDefinition.IsFamilyOrAssembly)
+ {
+ return ProtectedInternal;
+ }
+
+ if (fieldDefinition.IsFamilyAndAssembly)
+ {
+ return PrivateProtected;
+ }
+
+ throw new ArgumentException("The field definition seems to have no visibility.");
+ }
+
+ ///
+ /// Extracts generic parameters from a Cecil generic parameter provider (type or method)
+ /// and returns them as domain instances.
+ ///
public IEnumerable GetGenericParameters(
IGenericParameterProvider genericParameterProvider
)
@@ -615,76 +736,9 @@ IGenericParameterProvider genericParameterProvider
.Cast();
}
- internal GenericArgument CreateGenericArgumentFromTypeReference(TypeReference typeReference)
+ private GenericArgument CreateGenericArgumentFromTypeReference(TypeReference typeReference)
{
return new GenericArgument(GetOrCreateStubTypeInstanceFromTypeReference(typeReference));
}
-
- private void LoadBaseTask(IType cls, Type type, TypeDefinition typeDefinition)
- {
- if (typeDefinition == null)
- {
- return;
- }
-
- _loadTaskRegistry.Add(
- typeof(AddBaseClassDependency),
- new AddBaseClassDependency(cls, type, typeDefinition, this)
- );
- }
-
- private void LoadNonBaseTasks(
- ITypeInstance createdTypeInstance,
- Type type,
- TypeDefinition typeDefinition
- )
- {
- if (typeDefinition == null)
- {
- return;
- }
-
- _loadTaskRegistry.Add(
- typeof(AddMembers),
- new AddMembers(createdTypeInstance, typeDefinition, this, type.Members)
- );
- _loadTaskRegistry.Add(
- typeof(AddGenericParameterDependencies),
- new AddGenericParameterDependencies(type)
- );
- _loadTaskRegistry.Add(
- typeof(AddAttributesAndAttributeDependencies),
- new AddAttributesAndAttributeDependencies(
- createdTypeInstance.Type,
- typeDefinition,
- this
- )
- );
- _loadTaskRegistry.Add(
- typeof(AddFieldAndPropertyDependencies),
- new AddFieldAndPropertyDependencies(createdTypeInstance.Type)
- );
- _loadTaskRegistry.Add(
- typeof(AddMethodDependencies),
- new AddMethodDependencies(createdTypeInstance.Type, typeDefinition, this)
- );
- _loadTaskRegistry.Add(
- typeof(AddGenericArgumentDependencies),
- new AddGenericArgumentDependencies(type)
- );
- _loadTaskRegistry.Add(
- typeof(AddClassDependencies),
- new AddClassDependencies(
- createdTypeInstance.Type,
- typeDefinition,
- this,
- type.Dependencies
- )
- );
- _loadTaskRegistry.Add(
- typeof(AddBackwardsDependencies),
- new AddBackwardsDependencies(createdTypeInstance.Type)
- );
- }
}
}
diff --git a/ArchUnitNET/Loader/DotNetCoreAssemblyResolver.cs b/ArchUnitNET/Loader/DotNetCoreAssemblyResolver.cs
index 4eec205a3..a4e798d8b 100644
--- a/ArchUnitNET/Loader/DotNetCoreAssemblyResolver.cs
+++ b/ArchUnitNET/Loader/DotNetCoreAssemblyResolver.cs
@@ -12,7 +12,7 @@ internal class DotNetCoreAssemblyResolver : IAssemblyResolver
{
private readonly DefaultAssemblyResolver _defaultAssemblyResolver;
private readonly Dictionary _libraries;
- public string AssemblyPath = "";
+ public string AssemblyPath { get; set; } = "";
public DotNetCoreAssemblyResolver()
{
diff --git a/ArchUnitNET/Loader/FilterResult.cs b/ArchUnitNET/Loader/FilterResult.cs
index cc9dbbc9a..c99bcb5fa 100644
--- a/ArchUnitNET/Loader/FilterResult.cs
+++ b/ArchUnitNET/Loader/FilterResult.cs
@@ -16,22 +16,22 @@ public struct FilterResult
///
/// Load this assembly and traverse its dependencies
///
- public static FilterResult LoadAndContinue = new FilterResult(true, true);
+ public static readonly FilterResult LoadAndContinue = new FilterResult(true, true);
///
/// Do not load this assembly, but traverse its dependencies
///
- public static FilterResult SkipAndContinue = new FilterResult(true, false);
+ public static readonly FilterResult SkipAndContinue = new FilterResult(true, false);
///
/// Load this assembly and do not traverse its dependencies
///
- public static FilterResult LoadAndStop = new FilterResult(false, true);
+ public static readonly FilterResult LoadAndStop = new FilterResult(false, true);
///
/// Do not load this assembly and do not traverse its dependencies
///
- public static FilterResult DontLoadAndStop = new FilterResult(false, false);
+ public static readonly FilterResult DontLoadAndStop = new FilterResult(false, false);
private FilterResult(bool traverseDependencies, bool loadThisAssembly)
{
diff --git a/ArchUnitNET/Loader/LoadTaskRegistry.cs b/ArchUnitNET/Loader/LoadTaskRegistry.cs
deleted file mode 100644
index 9c7f1939b..000000000
--- a/ArchUnitNET/Loader/LoadTaskRegistry.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using ArchUnitNET.Domain.Extensions;
-using ArchUnitNET.Loader.LoadTasks;
-
-namespace ArchUnitNET.Loader
-{
- internal class LoadTaskRegistry
- {
- private readonly Dictionary> _tasksTodo =
- new Dictionary>();
-
- public void ExecuteTasks(List taskOrder)
- {
- var comparer = new TaskComparer(taskOrder);
-
- ICollection tasksTodoKeys = _tasksTodo.Keys;
-
- var prioritizedTodoKeys = tasksTodoKeys.OrderBy(taskType => taskType, comparer);
- prioritizedTodoKeys.ForEach(taskKey =>
- {
- if (!_tasksTodo.TryGetValue(taskKey, out var queueOfTasks))
- {
- return;
- }
-
- while (queueOfTasks.Count > 0)
- {
- var nextTask = queueOfTasks.Peek();
- nextTask.Execute();
- queueOfTasks.Dequeue();
- }
- });
- }
-
- public void Add(System.Type taskType, ILoadTask task)
- {
- if (taskType == null)
- {
- throw new ArgumentNullException(nameof(taskType));
- }
-
- if (task == null)
- {
- throw new ArgumentNullException(nameof(task));
- }
-
- var taskQueueExists = _tasksTodo.TryGetValue(taskType, out var queueOfTasksOfType);
- if (!taskQueueExists)
- {
- queueOfTasksOfType = new Queue();
- }
-
- queueOfTasksOfType.Enqueue(task);
- _tasksTodo[taskType] = queueOfTasksOfType;
- }
-
- private class TaskComparer : IComparer
- {
- private readonly List _taskOrder;
-
- public TaskComparer(List taskOrder)
- {
- _taskOrder = taskOrder;
- }
-
- public int Compare(System.Type x, System.Type y)
- {
- var a = _taskOrder.IndexOf(x);
- var b = _taskOrder.IndexOf(y);
-
- if (a > b)
- {
- return 1;
- }
-
- if (a < b)
- {
- return -1;
- }
-
- return 0;
- }
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddAttributesAndAttributeDependencies.cs b/ArchUnitNET/Loader/LoadTasks/AddAttributesAndAttributeDependencies.cs
deleted file mode 100644
index 83b73cf22..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddAttributesAndAttributeDependencies.cs
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright 2019 Florian Gather
-// Copyright 2019 Paula Ruiz
-// Copyright 2019 Fritz Brandhuber
-//
-// SPDX-License-Identifier: Apache-2.0
-
-using System.Collections.Generic;
-using System.Linq;
-using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Dependencies;
-using ArchUnitNET.Domain.Extensions;
-using JetBrains.Annotations;
-using Mono.Cecil;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal class AddAttributesAndAttributeDependencies : ILoadTask
- {
- private readonly IType _type;
- private readonly TypeDefinition _typeDefinition;
- private readonly TypeFactory _typeFactory;
-
- public AddAttributesAndAttributeDependencies(
- IType type,
- TypeDefinition typeDefinition,
- TypeFactory typeFactory
- )
- {
- _type = type;
- _typeDefinition = typeDefinition;
- _typeFactory = typeFactory;
- }
-
- public void Execute()
- {
- _typeDefinition.CustomAttributes.ForEach(
- AddAttributeArgumentReferenceDependenciesToOriginType
- );
- var typeAttributeInstances = CreateAttributesFromCustomAttributes(
- _typeDefinition.CustomAttributes
- )
- .ToList();
- _type.AttributeInstances.AddRange(typeAttributeInstances);
- var typeAttributeDependencies = typeAttributeInstances.Select(
- attributeInstance => new AttributeTypeDependency(_type, attributeInstance)
- );
- _type.Dependencies.AddRange(typeAttributeDependencies);
- SetUpAttributesForTypeGenericParameters();
- CollectAttributesForMembers();
- }
-
- private void SetUpAttributesForTypeGenericParameters()
- {
- foreach (var genericParameter in _typeDefinition.GenericParameters)
- {
- var param = _type.GenericParameters.First(parameter =>
- parameter.Name == genericParameter.Name
- );
- var attributeInstances = CreateAttributesFromCustomAttributes(
- genericParameter.CustomAttributes
- )
- .ToList();
- _type.AttributeInstances.AddRange(attributeInstances);
- param.AttributeInstances.AddRange(attributeInstances);
- var genericParameterAttributeDependencies = attributeInstances.Select(
- attributeInstance => new AttributeTypeDependency(_type, attributeInstance)
- );
- _type.Dependencies.AddRange(genericParameterAttributeDependencies);
- }
- }
-
- private void CollectAttributesForMembers()
- {
- _typeDefinition
- .Fields.Where(x => !x.IsBackingField() && !x.IsCompilerGenerated())
- .ForEach(SetUpAttributesForFields);
-
- _typeDefinition
- .Properties.Where(x => !x.IsCompilerGenerated())
- .ForEach(SetUpAttributesForProperties);
-
- _typeDefinition
- .Methods.Where(x => !x.IsCompilerGenerated())
- .ForEach(SetUpAttributesForMethods);
- }
-
- private void SetUpAttributesForFields(FieldDefinition fieldDefinition)
- {
- var fieldMember = _type
- .GetFieldMembers()
- .WhereFullNameIs(fieldDefinition.FullName)
- .RequiredNotNull();
- CollectMemberAttributesAndDependencies(
- fieldMember,
- fieldDefinition.CustomAttributes.ToList(),
- fieldMember.MemberDependencies
- );
- }
-
- private void SetUpAttributesForProperties(PropertyDefinition propertyDefinition)
- {
- var propertyMember = _type
- .GetPropertyMembers()
- .WhereFullNameIs(propertyDefinition.FullName)
- .RequiredNotNull();
- CollectMemberAttributesAndDependencies(
- propertyMember,
- propertyDefinition.CustomAttributes.ToList(),
- propertyMember.AttributeDependencies
- );
- }
-
- private void SetUpAttributesForMethods(MethodDefinition methodDefinition)
- {
- var methodMember = _type
- .GetMethodMembers()
- .WhereFullNameIs(methodDefinition.BuildFullName())
- .RequiredNotNull();
- var memberCustomAttributes = methodDefinition.GetAllMethodCustomAttributes().ToList();
- SetUpAttributesForMethodGenericParameters(methodDefinition, methodMember);
- CollectMemberAttributesAndDependencies(
- methodMember,
- memberCustomAttributes,
- methodMember.MemberDependencies
- );
- }
-
- private void SetUpAttributesForMethodGenericParameters(
- MethodDefinition methodDefinition,
- MethodMember methodMember
- )
- {
- foreach (var genericParameter in methodDefinition.GenericParameters)
- {
- var param = methodMember.GenericParameters.First(parameter =>
- parameter.Name == genericParameter.Name
- );
- var customAttributes = genericParameter.CustomAttributes;
- customAttributes.ForEach(AddAttributeArgumentReferenceDependenciesToOriginType);
- var attributeInstances = CreateAttributesFromCustomAttributes(customAttributes)
- .ToList();
- methodMember.AttributeInstances.AddRange(attributeInstances);
- param.AttributeInstances.AddRange(attributeInstances);
- var genericParameterAttributeDependencies = attributeInstances.Select(
- attributeInstance => new AttributeMemberDependency(
- methodMember,
- attributeInstance
- )
- );
- methodMember.MemberDependencies.AddRange(genericParameterAttributeDependencies);
- }
- }
-
- private void CollectMemberAttributesAndDependencies(
- IMember methodMember,
- List memberCustomAttributes,
- List attributeDependencies
- )
- {
- memberCustomAttributes.ForEach(AddAttributeArgumentReferenceDependenciesToOriginType);
- var memberAttributeInstances = CreateAttributesFromCustomAttributes(
- memberCustomAttributes
- )
- .ToList();
- methodMember.AttributeInstances.AddRange(memberAttributeInstances);
- var methodAttributeDependencies = CreateMemberAttributeDependencies(
- methodMember,
- memberAttributeInstances
- );
- attributeDependencies.AddRange(methodAttributeDependencies);
- }
-
- [NotNull]
- public IEnumerable CreateAttributesFromCustomAttributes(
- IEnumerable customAttributes
- )
- {
- return customAttributes
- .Where(customAttribute =>
- customAttribute.AttributeType.FullName
- != "Microsoft.CodeAnalysis.EmbeddedAttribute"
- && customAttribute.AttributeType.FullName
- != "System.Runtime.CompilerServices.NullableAttribute"
- && customAttribute.AttributeType.FullName
- != "System.Runtime.CompilerServices.NullableContextAttribute"
- )
- .Select(attr => attr.CreateAttributeFromCustomAttribute(_typeFactory));
- }
-
- [NotNull]
- private static IEnumerable CreateMemberAttributeDependencies(
- IMember member,
- IEnumerable attributes
- )
- {
- return attributes.Select(attributeInstance => new AttributeMemberDependency(
- member,
- attributeInstance
- ));
- }
-
- private void AddAttributeArgumentReferenceDependenciesToOriginType(
- ICustomAttribute customAttribute
- )
- {
- if (!customAttribute.HasConstructorArguments)
- {
- return;
- }
-
- var attributeConstructorArgs = customAttribute.ConstructorArguments;
- attributeConstructorArgs
- .Where(attributeArgument =>
- attributeArgument.Value is TypeReference typeReference
- && !typeReference.IsCompilerGenerated()
- )
- .Select(attributeArgument =>
- (typeReference: attributeArgument.Value as TypeReference, attributeArgument)
- )
- .ForEach(tuple =>
- {
- var argumentType = _typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- tuple.typeReference
- );
- var dependency = new TypeReferenceDependency(_type, argumentType);
- _type.Dependencies.Add(dependency);
- });
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddBackwardsDependencies.cs b/ArchUnitNET/Loader/LoadTasks/AddBackwardsDependencies.cs
deleted file mode 100644
index a50b51fc3..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddBackwardsDependencies.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Linq;
-using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Dependencies;
-using ArchUnitNET.Domain.Extensions;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal class AddBackwardsDependencies : ILoadTask
- {
- private readonly IType _type;
-
- public AddBackwardsDependencies(IType type)
- {
- _type = type;
- }
-
- public void Execute()
- {
- _type.Dependencies.ForEach(dependency =>
- dependency.Target.BackwardsDependencies.Add(dependency)
- );
-
- var memberMemberDependencies = _type
- .Members.SelectMany(member => member.MemberDependencies)
- .OfType();
- memberMemberDependencies.ForEach(memberDependency =>
- memberDependency.TargetMember.MemberBackwardsDependencies.Add(memberDependency)
- );
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddBaseClassDependency.cs b/ArchUnitNET/Loader/LoadTasks/AddBaseClassDependency.cs
deleted file mode 100644
index 9b8a3e299..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddBaseClassDependency.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Dependencies;
-using Mono.Cecil;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal class AddBaseClassDependency : ILoadTask
- {
- private readonly IType _cls;
- private readonly Type _type;
- private readonly TypeDefinition _typeDefinition;
- private readonly TypeFactory _typeFactory;
-
- public AddBaseClassDependency(
- IType cls,
- Type type,
- TypeDefinition typeDefinition,
- TypeFactory typeFactory
- )
- {
- _cls = cls;
- _type = type;
- _typeDefinition = typeDefinition;
- _typeFactory = typeFactory;
- }
-
- public void Execute()
- {
- var typeDefinitionBaseType = _typeDefinition?.BaseType;
-
- if (typeDefinitionBaseType == null)
- {
- return;
- }
-
- var baseType = _typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- typeDefinitionBaseType
- );
- if (!(baseType.Type is Class baseClass))
- {
- return;
- }
-
- var dependency = new InheritsBaseClassDependency(
- _cls,
- new TypeInstance(
- baseClass,
- baseType.GenericArguments,
- baseType.ArrayDimensions
- )
- );
- _type.Dependencies.Add(dependency);
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddClassDependencies.cs b/ArchUnitNET/Loader/LoadTasks/AddClassDependencies.cs
deleted file mode 100644
index f34eb2112..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddClassDependencies.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Dependencies;
-using ArchUnitNET.Domain.Extensions;
-using Mono.Cecil;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal class AddClassDependencies : ILoadTask
- {
- private readonly List _dependencies;
- private readonly IType _type;
- private readonly TypeDefinition _typeDefinition;
- private readonly TypeFactory _typeFactory;
-
- public AddClassDependencies(
- IType type,
- TypeDefinition typeDefinition,
- TypeFactory typeFactory,
- List dependencies
- )
- {
- _type = type;
- _typeDefinition = typeDefinition;
- _typeFactory = typeFactory;
- _dependencies = dependencies;
- }
-
- public void Execute()
- {
- AddInterfaceDependencies();
- AddMemberDependencies();
- }
-
- private void AddMemberDependencies()
- {
- _type.Members.ForEach(member =>
- {
- _dependencies.AddRange(member.Dependencies);
- });
- }
-
- private void AddInterfaceDependencies()
- {
- GetInterfacesImplementedByClass(_typeDefinition)
- .ForEach(target =>
- {
- var targetType = _typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- target
- );
- _dependencies.Add(new ImplementsInterfaceDependency(_type, targetType));
- });
- }
-
- private static IEnumerable GetInterfacesImplementedByClass(
- TypeDefinition typeDefinition
- )
- {
- var baseType = typeDefinition.BaseType?.Resolve();
- var baseInterfaces =
- baseType != null
- ? GetInterfacesImplementedByClass(baseType)
- : new List();
-
- return typeDefinition
- .Interfaces.Select(implementation => implementation.InterfaceType)
- .Concat(baseInterfaces);
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddFieldAndPropertyDependencies.cs b/ArchUnitNET/Loader/LoadTasks/AddFieldAndPropertyDependencies.cs
deleted file mode 100644
index 041c072ba..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddFieldAndPropertyDependencies.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Dependencies;
-using ArchUnitNET.Domain.Extensions;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal class AddFieldAndPropertyDependencies : ILoadTask
- {
- private readonly IType _type;
-
- public AddFieldAndPropertyDependencies(IType type)
- {
- _type = type;
- }
-
- public void Execute()
- {
- _type
- .GetFieldMembers()
- .ForEach(field =>
- {
- var dependency = new FieldTypeDependency(field);
- AddDependencyIfMissing(field, dependency);
- });
-
- _type
- .GetPropertyMembers()
- .ForEach(property =>
- {
- var dependency = new PropertyTypeDependency(property);
- AddDependencyIfMissing(property, dependency);
- });
- }
-
- private static void AddDependencyIfMissing(IMember member, IMemberTypeDependency dependency)
- {
- if (!member.MemberDependencies.Contains(dependency))
- {
- member.MemberDependencies.Add(dependency);
- }
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddGenericArgumentDependencies.cs b/ArchUnitNET/Loader/LoadTasks/AddGenericArgumentDependencies.cs
deleted file mode 100644
index c109be34b..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddGenericArgumentDependencies.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Dependencies;
-using JetBrains.Annotations;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- public class AddGenericArgumentDependencies : ILoadTask
- {
- [NotNull]
- private readonly IType _type;
-
- public AddGenericArgumentDependencies([NotNull] IType type)
- {
- _type = type;
- }
-
- public void Execute()
- {
- AddTypeGenericArgumentDependencies();
- AddMemberGenericArgumentDependencies();
-
- var typeDependencies = new List();
- foreach (var dependency in _type.Dependencies)
- {
- FindGenericArgumentsInTypeDependenciesRecursive(
- dependency.TargetGenericArguments,
- typeDependencies
- );
- }
-
- _type.Dependencies.AddRange(typeDependencies);
-
- foreach (var member in _type.Members)
- {
- var memberDependencies = new List();
- foreach (var dependency in member.Dependencies)
- {
- FindGenericArgumentsInMemberDependenciesRecursive(
- member,
- dependency.TargetGenericArguments,
- memberDependencies
- );
- }
-
- member.MemberDependencies.AddRange(memberDependencies);
- }
- }
-
- private void AddTypeGenericArgumentDependencies()
- {
- foreach (var parameter in _type.GenericParameters)
- {
- _type.Dependencies.AddRange(parameter.Dependencies);
- }
- }
-
- private void FindGenericArgumentsInTypeDependenciesRecursive(
- IEnumerable targetGenericArguments,
- ICollection createdDependencies
- )
- {
- foreach (
- var genericArgument in targetGenericArguments.Where(argument =>
- !argument.Type.IsGenericParameter
- )
- )
- {
- createdDependencies.Add(new GenericArgumentTypeDependency(_type, genericArgument));
- FindGenericArgumentsInTypeDependenciesRecursive(
- genericArgument.GenericArguments,
- createdDependencies
- );
- }
- }
-
- private void AddMemberGenericArgumentDependencies()
- {
- foreach (var member in _type.Members)
- {
- foreach (var parameter in member.GenericParameters)
- {
- member.MemberDependencies.AddRange(
- parameter.Dependencies.Cast()
- );
- }
- }
- }
-
- private static void FindGenericArgumentsInMemberDependenciesRecursive(
- IMember member,
- IEnumerable targetGenericArguments,
- ICollection createdDependencies
- )
- {
- foreach (
- var genericArgument in targetGenericArguments.Where(argument =>
- !argument.Type.IsGenericParameter
- )
- )
- {
- createdDependencies.Add(
- new GenericArgumentMemberDependency(member, genericArgument)
- );
- FindGenericArgumentsInMemberDependenciesRecursive(
- member,
- genericArgument.GenericArguments,
- createdDependencies
- );
- }
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddGenericParameterDependencies.cs b/ArchUnitNET/Loader/LoadTasks/AddGenericParameterDependencies.cs
deleted file mode 100644
index fc9ed1ae4..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddGenericParameterDependencies.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Dependencies;
-using JetBrains.Annotations;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- public class AddGenericParameterDependencies : ILoadTask
- {
- [NotNull]
- private readonly IType _type;
-
- public AddGenericParameterDependencies([NotNull] IType type)
- {
- _type = type;
- }
-
- public void Execute()
- {
- AddTypeGenericParameterDependencies();
- AddMemberGenericParameterDependencies();
- }
-
- private void AddTypeGenericParameterDependencies()
- {
- foreach (var genericParameter in _type.GenericParameters)
- {
- genericParameter.AssignDeclarer(_type);
- foreach (var typeInstanceConstraint in genericParameter.TypeInstanceConstraints)
- {
- var dependency = new TypeGenericParameterTypeConstraintDependency(
- genericParameter,
- typeInstanceConstraint
- );
- genericParameter.Dependencies.Add(dependency);
- }
- }
- }
-
- private void AddMemberGenericParameterDependencies()
- {
- foreach (var member in _type.Members)
- {
- foreach (var genericParameter in member.GenericParameters)
- {
- genericParameter.AssignDeclarer(member);
- foreach (var typeInstanceConstraint in genericParameter.TypeInstanceConstraints)
- {
- var dependency = new MemberGenericParameterTypeConstraintDependency(
- genericParameter,
- typeInstanceConstraint
- );
- genericParameter.Dependencies.Add(dependency);
- }
- }
- }
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddMembers.cs b/ArchUnitNET/Loader/LoadTasks/AddMembers.cs
deleted file mode 100644
index bbb0d86fe..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddMembers.cs
+++ /dev/null
@@ -1,173 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using ArchUnitNET.Domain;
-using JetBrains.Annotations;
-using Mono.Cecil;
-using static ArchUnitNET.Domain.Visibility;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal class AddMembers : ILoadTask
- {
- private readonly MemberList _memberList;
- private readonly ITypeInstance _typeInstance;
- private readonly TypeDefinition _typeDefinition;
- private readonly TypeFactory _typeFactory;
-
- public AddMembers(
- ITypeInstance typeInstance,
- TypeDefinition typeDefinition,
- TypeFactory typeFactory,
- MemberList memberList
- )
- {
- _typeInstance = typeInstance;
- _typeDefinition = typeDefinition;
- _typeFactory = typeFactory;
- _memberList = memberList;
- }
-
- public void Execute()
- {
- var members = CreateMembers(_typeDefinition);
- _memberList.AddRange(members);
- }
-
- [NotNull]
- private IEnumerable CreateMembers([NotNull] TypeDefinition typeDefinition)
- {
- return typeDefinition
- .Fields.Where(fieldDefinition => !fieldDefinition.IsBackingField())
- .Select(CreateFieldMember)
- .Concat(
- typeDefinition
- .Properties.Select(CreatePropertyMember)
- .Concat(
- typeDefinition.Methods.Select(method =>
- _typeFactory
- .GetOrCreateMethodMemberFromMethodReference(
- _typeInstance,
- method
- )
- .Member
- )
- )
- )
- .Where(member => !member.IsCompilerGenerated);
- }
-
- [NotNull]
- private IMember CreateFieldMember([NotNull] FieldDefinition fieldDefinition)
- {
- var typeReference = fieldDefinition.FieldType;
- var fieldType = _typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- typeReference
- );
- var visibility = GetVisibilityFromFieldDefinition(fieldDefinition);
- var isCompilerGenerated = fieldDefinition.IsCompilerGenerated();
- var writeAccessor = GetWriteAccessor(fieldDefinition);
- return new FieldMember(
- _typeInstance.Type,
- fieldDefinition.Name,
- fieldDefinition.FullName,
- visibility,
- fieldType,
- isCompilerGenerated,
- fieldDefinition.IsStatic,
- writeAccessor
- );
- }
-
- [NotNull]
- private IMember CreatePropertyMember(PropertyDefinition propertyDefinition)
- {
- var typeReference = propertyDefinition.PropertyType;
- var propertyType = _typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- typeReference
- );
- var isCompilerGenerated = propertyDefinition.IsCompilerGenerated();
- var isStatic =
- (propertyDefinition.SetMethod != null && propertyDefinition.SetMethod.IsStatic)
- || (propertyDefinition.GetMethod != null && propertyDefinition.GetMethod.IsStatic);
- var writeAccessor = GetWriteAccessor(propertyDefinition);
- return new PropertyMember(
- _typeInstance.Type,
- propertyDefinition.Name,
- propertyDefinition.FullName,
- propertyType,
- isCompilerGenerated,
- isStatic,
- writeAccessor
- );
- }
-
- private static Visibility GetVisibilityFromFieldDefinition(
- [NotNull] FieldDefinition fieldDefinition
- )
- {
- if (fieldDefinition.IsPublic)
- {
- return Public;
- }
-
- if (fieldDefinition.IsPrivate)
- {
- return Private;
- }
-
- if (fieldDefinition.IsFamily)
- {
- return Protected;
- }
-
- if (fieldDefinition.IsAssembly)
- {
- return Internal;
- }
-
- if (fieldDefinition.IsFamilyOrAssembly)
- {
- return ProtectedInternal;
- }
-
- if (fieldDefinition.IsFamilyAndAssembly)
- {
- return PrivateProtected;
- }
-
- throw new ArgumentException("The field definition seems to have no visibility.");
- }
-
- private static Writability GetWriteAccessor([NotNull] FieldDefinition fieldDefinition)
- {
- return fieldDefinition.IsInitOnly ? Writability.ReadOnly : Writability.Writable;
- }
-
- private static Writability GetWriteAccessor([NotNull] PropertyDefinition propertyDefinition)
- {
- bool isReadOnly = propertyDefinition.SetMethod == null;
-
- if (isReadOnly)
- {
- return Writability.ReadOnly;
- }
-
- bool isInitSetter = CheckPropertyHasInitSetterInNetStandardCompatibleWay(
- propertyDefinition
- );
- return isInitSetter ? Writability.InitOnly : Writability.Writable;
- }
-
- private static bool CheckPropertyHasInitSetterInNetStandardCompatibleWay(
- PropertyDefinition propertyDefinition
- )
- {
- return propertyDefinition.SetMethod?.ReturnType.IsRequiredModifier == true
- && ((RequiredModifierType)propertyDefinition.SetMethod.ReturnType)
- .ModifierType
- .FullName == "System.Runtime.CompilerServices.IsExternalInit";
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddMethodDependencies.cs b/ArchUnitNET/Loader/LoadTasks/AddMethodDependencies.cs
deleted file mode 100644
index 5878c6d28..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddMethodDependencies.cs
+++ /dev/null
@@ -1,477 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Dependencies;
-using ArchUnitNET.Domain.Extensions;
-using JetBrains.Annotations;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal class AddMethodDependencies : ILoadTask
- {
- private readonly IType _type;
- private readonly TypeDefinition _typeDefinition;
- private readonly TypeFactory _typeFactory;
-
- public AddMethodDependencies(
- IType type,
- TypeDefinition typeDefinition,
- TypeFactory typeFactory
- )
- {
- _type = type;
- _typeDefinition = typeDefinition;
- _typeFactory = typeFactory;
- }
-
- public void Execute()
- {
- _typeDefinition
- .Methods.Where(methodDefinition =>
- _type.GetMemberWithFullName(methodDefinition.BuildFullName()) is MethodMember
- )
- .Select(definition =>
- (
- methodMember: _type.GetMemberWithFullName(definition.BuildFullName())
- as MethodMember,
- methodDefinition: definition
- )
- )
- .Select(tuple =>
- {
- var (methodMember, methodDefinition) = tuple;
- var dependencies = CreateMethodSignatureDependencies(
- methodDefinition,
- methodMember
- )
- .Concat(CreateMethodBodyDependencies(methodDefinition, methodMember));
- if (methodDefinition.IsSetter || methodDefinition.IsGetter)
- {
- AssignDependenciesToProperty(methodMember, methodDefinition);
- }
-
- return (methodMember, dependencies);
- })
- .ForEach(tuple =>
- {
- var (methodMember, dependencies) = tuple;
- methodMember.MemberDependencies.AddRange(dependencies);
- });
- }
-
- private void AssignDependenciesToProperty(
- MethodMember methodMember,
- MethodDefinition methodDefinition
- )
- {
- var methodForm = methodDefinition.GetMethodForm();
- var matchFunction = GetMatchFunction(methodForm);
- matchFunction.RequiredNotNull();
-
- var accessedProperty = MatchToPropertyMember(
- methodMember.Name,
- methodMember.FullName,
- matchFunction
- );
- if (accessedProperty == null)
- {
- return;
- }
-
- accessedProperty.IsVirtual = accessedProperty.IsVirtual || methodMember.IsVirtual;
-
- switch (methodForm)
- {
- case MethodForm.Getter:
- accessedProperty.Getter = methodMember;
- break;
- case MethodForm.Setter:
- accessedProperty.Setter = methodMember;
- break;
- }
-
- var methodBody = methodDefinition.Body;
-
- if (methodBody == null)
- {
- return;
- }
-
- if (
- !methodBody
- .Instructions.Select(instruction => instruction.Operand)
- .OfType()
- .Any(definition => definition.IsBackingField())
- )
- {
- accessedProperty.IsAutoProperty = false;
- }
- }
-
- [NotNull]
- private IEnumerable CreateMethodSignatureDependencies(
- MethodReference methodReference,
- MethodMember methodMember
- )
- {
- var returnType = methodReference.GetReturnType(_typeFactory);
- return (returnType != null ? new[] { returnType } : Array.Empty>())
- .Concat(methodReference.GetParameters(_typeFactory))
- .Concat(methodReference.GetGenericParameters(_typeFactory))
- .Distinct()
- .Select(signatureType => new MethodSignatureDependency(
- methodMember,
- signatureType
- ));
- }
-
- [NotNull]
- private IEnumerable CreateMethodBodyDependencies(
- MethodDefinition methodDefinition,
- MethodMember methodMember
- )
- {
- var methodBody = methodDefinition.Body;
- if (methodBody == null)
- {
- yield break;
- }
-
- var visitedMethodReferences = new List { methodDefinition };
- var bodyTypes = new List>();
-
- if (methodDefinition.IsAsync())
- {
- HandleAsync(
- out methodDefinition,
- ref methodBody,
- bodyTypes,
- visitedMethodReferences
- );
- }
-
- if (methodDefinition.IsIterator())
- {
- HandleIterator(
- out methodDefinition,
- ref methodBody,
- bodyTypes,
- visitedMethodReferences
- );
- }
-
- bodyTypes.AddRange(methodDefinition.GetBodyTypes(_typeFactory).ToList());
-
- var castTypes = methodDefinition.GetCastTypes(_typeFactory).ToList();
-
- var typeCheckTypes = methodDefinition.GetTypeCheckTypes(_typeFactory).ToList();
-
- var metaDataTypes = methodDefinition.GetMetaDataTypes(_typeFactory).ToList();
-
- var accessedFieldMembers = methodDefinition
- .GetAccessedFieldMembers(_typeFactory)
- .ToList();
-
- var calledMethodMembers = CreateMethodBodyDependenciesRecursive(
- methodBody,
- visitedMethodReferences,
- bodyTypes,
- castTypes,
- typeCheckTypes,
- metaDataTypes,
- accessedFieldMembers
- );
-
- foreach (
- var calledMethodMember in calledMethodMembers
- .Where(method => !method.Member.IsCompilerGenerated)
- .Distinct()
- )
- {
- yield return new MethodCallDependency(methodMember, calledMethodMember);
- }
-
- foreach (
- var bodyType in bodyTypes
- .Where(instance => !instance.Type.IsCompilerGenerated)
- .Distinct()
- )
- {
- yield return new BodyTypeMemberDependency(methodMember, bodyType);
- }
-
- foreach (
- var castType in castTypes
- .Where(instance => !instance.Type.IsCompilerGenerated)
- .Distinct()
- )
- {
- yield return new CastTypeDependency(methodMember, castType);
- }
-
- foreach (
- var typeCheckType in typeCheckTypes
- .Where(instance => !instance.Type.IsCompilerGenerated)
- .Distinct()
- )
- {
- yield return new TypeCheckDependency(methodMember, typeCheckType);
- }
-
- foreach (
- var metaDataType in metaDataTypes
- .Where(instance => !instance.Type.IsCompilerGenerated)
- .Distinct()
- )
- {
- yield return new MetaDataDependency(methodMember, metaDataType);
- }
-
- foreach (
- var fieldMember in accessedFieldMembers
- .Where(field => !field.IsCompilerGenerated)
- .Distinct()
- )
- {
- yield return new AccessFieldDependency(methodMember, fieldMember);
- }
- }
-
- private IEnumerable CreateMethodBodyDependenciesRecursive(
- MethodBody methodBody,
- ICollection visitedMethodReferences,
- List> bodyTypes,
- List> castTypes,
- List> typeCheckTypes,
- List> metaDataTypes,
- List accessedFieldMembers
- )
- {
- var calledMethodReferences = methodBody
- .Instructions.Select(instruction => instruction.Operand)
- .OfType();
-
- foreach (
- var calledMethodReference in calledMethodReferences.Except(visitedMethodReferences)
- )
- {
- visitedMethodReferences.Add(calledMethodReference);
-
- var calledType = _typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- calledMethodReference.DeclaringType
- );
- var calledMethodMember = _typeFactory.GetOrCreateMethodMemberFromMethodReference(
- calledType,
- calledMethodReference
- );
-
- bodyTypes.AddRange(calledMethodMember.MemberGenericArguments);
-
- if (calledMethodReference.IsCompilerGenerated())
- {
- MethodDefinition calledMethodDefinition;
- try
- {
- calledMethodDefinition = calledMethodReference.Resolve();
- }
- catch (AssemblyResolutionException)
- {
- calledMethodDefinition = null;
- }
-
- if (calledMethodDefinition?.Body == null)
- {
- //MethodReference to compiler generated type not resolvable, skip
- continue;
- }
-
- var calledMethodBody = calledMethodDefinition.Body;
-
- if (calledMethodDefinition.IsIterator())
- {
- HandleIterator(
- out calledMethodDefinition,
- ref calledMethodBody,
- bodyTypes,
- visitedMethodReferences
- );
- }
-
- bodyTypes.AddRange(calledMethodDefinition.GetBodyTypes(_typeFactory));
- castTypes.AddRange(calledMethodDefinition.GetCastTypes(_typeFactory));
- typeCheckTypes.AddRange(calledMethodDefinition.GetTypeCheckTypes(_typeFactory));
- metaDataTypes.AddRange(calledMethodDefinition.GetMetaDataTypes(_typeFactory));
- accessedFieldMembers.AddRange(
- calledMethodDefinition.GetAccessedFieldMembers(_typeFactory)
- );
-
- foreach (
- var dep in CreateMethodBodyDependenciesRecursive(
- calledMethodBody,
- visitedMethodReferences,
- bodyTypes,
- castTypes,
- typeCheckTypes,
- metaDataTypes,
- accessedFieldMembers
- )
- )
- {
- yield return dep;
- }
- }
- else
- {
- yield return calledMethodMember;
- }
- }
- }
-
- private void HandleIterator(
- out MethodDefinition methodDefinition,
- ref MethodBody methodBody,
- List> bodyTypes,
- ICollection visitedMethodReferences
- )
- {
- var compilerGeneratedGeneratorObject = (
- (MethodReference)methodBody.Instructions.First(inst => inst.IsNewObjectOp()).Operand
- ).DeclaringType.Resolve();
- methodDefinition = compilerGeneratedGeneratorObject.Methods.First(method =>
- method.Name == nameof(IEnumerator.MoveNext)
- );
- visitedMethodReferences.Add(methodDefinition);
- methodBody = methodDefinition.Body;
-
- var fieldsExceptGeneratorStateInfo = compilerGeneratedGeneratorObject.Fields.Where(
- field =>
- !(
- field.Name.EndsWith("__state")
- || field.Name.EndsWith("__current")
- || field.Name.EndsWith("__initialThreadId")
- || field.Name.EndsWith("__this")
- )
- );
-
- bodyTypes.AddRange(
- fieldsExceptGeneratorStateInfo.Select(bodyField =>
- _typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(bodyField.FieldType)
- )
- );
- }
-
- private void HandleAsync(
- out MethodDefinition methodDefinition,
- ref MethodBody methodBody,
- List> bodyTypes,
- ICollection visitedMethodReferences
- )
- {
- var compilerGeneratedGeneratorObject = (
- (MethodReference)
- methodBody.Instructions.FirstOrDefault(inst => inst.IsNewObjectOp())?.Operand
- )?.DeclaringType.Resolve();
-
- if (compilerGeneratedGeneratorObject == null)
- {
- methodDefinition = methodBody.Method;
- return;
- }
-
- methodDefinition = compilerGeneratedGeneratorObject.Methods.First(method =>
- method.Name == nameof(IAsyncStateMachine.MoveNext)
- );
-
- visitedMethodReferences.Add(methodDefinition);
- methodBody = methodDefinition.Body;
-
- var fieldsExceptGeneratorStateInfo = compilerGeneratedGeneratorObject
- .Fields.Where(field =>
- !(
- field.Name.EndsWith("__state")
- || field.Name.EndsWith("__builder")
- || field.Name.EndsWith("__this")
- )
- )
- .ToArray();
-
- bodyTypes.AddRange(
- fieldsExceptGeneratorStateInfo.Select(bodyField =>
- _typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(bodyField.FieldType)
- )
- );
- }
-
- private static MatchFunction GetMatchFunction(MethodForm methodForm)
- {
- MatchFunction matchFunction;
- switch (methodForm)
- {
- case MethodForm.Getter:
- matchFunction = new MatchFunction(RegexUtils.MatchGetPropertyName);
- break;
- case MethodForm.Setter:
- matchFunction = new MatchFunction(RegexUtils.MatchSetPropertyName);
- break;
- default:
- matchFunction = null;
- break;
- }
-
- return matchFunction.RequiredNotNull();
- }
-
- private PropertyMember MatchToPropertyMember(
- string name,
- string fullName,
- MatchFunction matchFunction
- )
- {
- try
- {
- var accessedMemberName = matchFunction.MatchNameFunction(name);
- if (accessedMemberName != null)
- {
- var foundNameMatches = _type
- .GetPropertyMembersWithName(accessedMemberName)
- .SingleOrDefault();
- if (foundNameMatches != null)
- {
- return foundNameMatches;
- }
- }
- }
- catch (InvalidOperationException) { }
-
- var accessedMemberFullName = matchFunction.MatchNameFunction(fullName);
- return accessedMemberFullName != null
- ? GetPropertyMemberWithFullNameEndingWith(_type, accessedMemberFullName)
- : null;
- }
-
- private PropertyMember GetPropertyMemberWithFullNameEndingWith(
- IType type,
- string detailedName
- )
- {
- return type
- .Members.OfType()
- .FirstOrDefault(propertyMember => propertyMember.FullName.EndsWith(detailedName));
- }
- }
-
- public class MatchFunction
- {
- public MatchFunction(Func matchNameFunction)
- {
- MatchNameFunction = matchNameFunction;
- }
-
- public Func MatchNameFunction { get; }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/AddTypesToNamespaces.cs b/ArchUnitNET/Loader/LoadTasks/AddTypesToNamespaces.cs
deleted file mode 100644
index d934104f5..000000000
--- a/ArchUnitNET/Loader/LoadTasks/AddTypesToNamespaces.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using ArchUnitNET.Domain;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal class AddTypesToNamespaces : ILoadTask
- {
- private readonly List _types;
-
- public AddTypesToNamespaces(List types)
- {
- _types = types;
- }
-
- public void Execute()
- {
- foreach (var type in _types)
- {
- ((List)type.Namespace.Types).Add(type);
- }
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/CollectAssemblyAttributes.cs b/ArchUnitNET/Loader/LoadTasks/CollectAssemblyAttributes.cs
deleted file mode 100644
index c9ffd2bc5..000000000
--- a/ArchUnitNET/Loader/LoadTasks/CollectAssemblyAttributes.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System.Linq;
-using ArchUnitNET.Domain;
-using Mono.Cecil;
-
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal class CollectAssemblyAttributes : ILoadTask
- {
- private readonly Assembly _assembly;
- private readonly AssemblyDefinition _assemblyDefinition;
- private readonly TypeFactory _typeFactory;
-
- public CollectAssemblyAttributes(
- Assembly assembly,
- AssemblyDefinition assemblyDefinition,
- TypeFactory typeFactory
- )
- {
- _assembly = assembly;
- _assemblyDefinition = assemblyDefinition;
- _typeFactory = typeFactory;
- }
-
- public void Execute()
- {
- var attributeInstances = _assemblyDefinition
- .CustomAttributes.Select(attr =>
- attr.CreateAttributeFromCustomAttribute(_typeFactory)
- )
- .ToList();
- _assembly.AttributeInstances.AddRange(attributeInstances);
- }
- }
-}
diff --git a/ArchUnitNET/Loader/LoadTasks/ILoadTask.cs b/ArchUnitNET/Loader/LoadTasks/ILoadTask.cs
deleted file mode 100644
index 1c491b16d..000000000
--- a/ArchUnitNET/Loader/LoadTasks/ILoadTask.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace ArchUnitNET.Loader.LoadTasks
-{
- internal interface ILoadTask
- {
- void Execute();
- }
-}
diff --git a/ArchUnitNET/Loader/MonoCecilAttributeExtensions.cs b/ArchUnitNET/Loader/MonoCecilAttributeExtensions.cs
index b6ea30095..9b1e41ea7 100644
--- a/ArchUnitNET/Loader/MonoCecilAttributeExtensions.cs
+++ b/ArchUnitNET/Loader/MonoCecilAttributeExtensions.cs
@@ -13,11 +13,11 @@ internal static class MonoCecilAttributeExtensions
[NotNull]
public static AttributeInstance CreateAttributeFromCustomAttribute(
this CustomAttribute customAttribute,
- TypeFactory typeFactory
+ DomainResolver domainResolver
)
{
var attributeTypeReference = customAttribute.AttributeType;
- var attributeType = typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
+ var attributeType = domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
attributeTypeReference
);
var attribute = attributeType.Type as Attribute;
@@ -41,7 +41,7 @@ TypeFactory typeFactory
{
HandleAttributeArgument(
constructorArgument,
- typeFactory,
+ domainResolver,
out var value,
out var type
);
@@ -53,7 +53,7 @@ out var type
var name = namedArgument.Name;
HandleAttributeArgument(
namedArgument.Argument,
- typeFactory,
+ domainResolver,
out var value,
out var type
);
@@ -65,7 +65,7 @@ out var type
private static void HandleAttributeArgument(
CustomAttributeArgument argument,
- TypeFactory typeFactory,
+ DomainResolver domainResolver,
out object value,
out ITypeInstance type
)
@@ -75,21 +75,21 @@ out ITypeInstance type
argument = arg;
}
- type = typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(argument.Type);
+ type = domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(argument.Type);
if (argument.Value is IEnumerable attArgEnumerable)
{
value = (
from attArg in attArgEnumerable
select attArg.Value is TypeReference tr
- ? typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(tr)
+ ? domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(tr)
: attArg.Value
).ToArray();
}
else
{
value = argument.Value is TypeReference tr
- ? typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(tr)
+ ? domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(tr)
: argument.Value;
}
}
diff --git a/ArchUnitNET/Loader/MonoCecilMemberExtensions.cs b/ArchUnitNET/Loader/MonoCecilMemberExtensions.cs
index 20379af43..061dc5a28 100644
--- a/ArchUnitNET/Loader/MonoCecilMemberExtensions.cs
+++ b/ArchUnitNET/Loader/MonoCecilMemberExtensions.cs
@@ -3,7 +3,6 @@
using System.Linq;
using System.Text;
using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Exceptions;
using ArchUnitNET.Domain.Extensions;
using JetBrains.Annotations;
using Mono.Cecil;
@@ -30,11 +29,18 @@ internal static class MonoCecilMemberExtensions
internal static string BuildFullName(this MethodReference methodReference)
{
- return methodReference.FullName
- + methodReference.GenericParameters.Aggregate(
- string.Empty,
- (current, newElement) => current + "<" + newElement.Name + ">"
- );
+ if (!methodReference.HasGenericParameters)
+ {
+ return methodReference.FullName;
+ }
+
+ var sb = new StringBuilder(methodReference.FullName);
+ foreach (var p in methodReference.GenericParameters)
+ {
+ sb.Append('<').Append(p.Name).Append('>');
+ }
+
+ return sb.ToString();
}
[NotNull]
@@ -84,120 +90,187 @@ this MethodDefinition methodDefinition
internal static ITypeInstance GetReturnType(
this MethodReference methodReference,
- TypeFactory typeFactory
+ DomainResolver domainResolver
) =>
ReturnsVoid(methodReference)
? null
- : typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
+ : domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
methodReference.MethodReturnType.ReturnType
);
[NotNull]
internal static IEnumerable> GetParameters(
this MethodReference method,
- TypeFactory typeFactory
+ DomainResolver domainResolver
) =>
method.Parameters.Select(parameter =>
- typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(parameter.ParameterType)
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(parameter.ParameterType)
);
[NotNull]
internal static IEnumerable> GetGenericParameters(
this MethodReference method,
- TypeFactory typeFactory
+ DomainResolver domainResolver
) =>
method.GenericParameters.Select(
- typeFactory.GetOrCreateStubTypeInstanceFromTypeReference
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference
);
+ ///
+ /// Result of a single-pass scan of a method body.
+ ///
+ internal struct MethodBodyScanResult
+ {
+ internal readonly List> BodyTypes;
+ internal readonly List> CastTypes;
+ internal readonly List> MetaDataTypes;
+ internal readonly List> TypeCheckTypes;
+ internal readonly List AccessedFieldMembers;
+
+ internal MethodBodyScanResult(
+ List> bodyTypes,
+ List> castTypes,
+ List> metaDataTypes,
+ List> typeCheckTypes,
+ List accessedFieldMembers
+ )
+ {
+ BodyTypes = bodyTypes;
+ CastTypes = castTypes;
+ MetaDataTypes = metaDataTypes;
+ TypeCheckTypes = typeCheckTypes;
+ AccessedFieldMembers = accessedFieldMembers;
+ }
+ }
+
+ ///
+ /// Scans the method body in a single pass, collecting body types, cast types,
+ /// metadata types, type-check types, accessed field members, and local variable types.
+ ///
[NotNull]
- internal static IEnumerable> GetBodyTypes(
+ internal static MethodBodyScanResult ScanMethodBody(
this MethodDefinition methodDefinition,
- TypeFactory typeFactory
+ DomainResolver domainResolver
)
{
- var instructions =
- methodDefinition.Body?.Instructions ?? Enumerable.Empty();
+ var bodyTypes = new List>();
+ var castTypes = new List>();
+ var metaDataTypes = new List>();
+ var typeCheckTypes = new List>();
+ var accessedFieldMembers = new List();
- var bodyTypes = instructions
- .Where(inst =>
- BodyTypeOpCodes.Contains(inst.OpCode) && inst.Operand is TypeReference
- )
- .Select(inst =>
- typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- (TypeReference)inst.Operand
- )
+ var body = methodDefinition.Body;
+ if (body != null)
+ {
+ // Collect local variable types
+ var seenBodyTypes = new HashSet>();
+ bodyTypes.AddRange(
+ body.Variables.Select(variableDefinition =>
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ variableDefinition.VariableType
+ )
+ )
+ .Where(typeInstance => seenBodyTypes.Add(typeInstance))
);
- //OpCodes.Ldstr should create a dependency to string, but it does not have a TypeReference as Operand so no Type can be created
+ // Single pass over instructions
+ var seenFieldRefs = new HashSet(
+ FieldReferenceNameComparer.Instance
+ );
+ foreach (var instruction in body.Instructions)
+ {
+ var opCode = instruction.OpCode;
+ var operand = instruction.Operand;
- bodyTypes = bodyTypes
- .Union(
- methodDefinition.Body?.Variables.Select(variableDefinition =>
+ switch (operand)
{
- var variableTypeReference = variableDefinition.VariableType;
- return typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- variableTypeReference
- );
- }) ?? Enumerable.Empty>()
- )
- .Distinct();
+ case TypeReference typeReference when opCode == OpCodes.Castclass:
+ castTypes.Add(
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ typeReference
+ )
+ );
+ break;
+ case TypeReference typeReference when opCode == OpCodes.Ldtoken:
+ metaDataTypes.Add(
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ typeReference
+ )
+ );
+ break;
+ case TypeReference typeReference when opCode == OpCodes.Isinst:
+ typeCheckTypes.Add(
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ typeReference
+ )
+ );
+ break;
+ case TypeReference typeReference:
+ {
+ if (BodyTypeOpCodes.Contains(opCode))
+ {
+ var bodyTypeInstance =
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ typeReference
+ );
+ if (seenBodyTypes.Add(bodyTypeInstance))
+ {
+ bodyTypes.Add(bodyTypeInstance);
+ }
+ }
+
+ break;
+ }
+ case FieldReference fieldReference when !seenFieldRefs.Add(fieldReference):
+ continue;
+ case FieldReference fieldReference:
+ {
+ var declaringType =
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ fieldReference.DeclaringType
+ );
+ accessedFieldMembers.Add(
+ domainResolver.GetOrCreateFieldMember(
+ declaringType.Type,
+ fieldReference
+ )
+ );
+ break;
+ }
+ }
+ }
+ }
- return bodyTypes;
+ return new MethodBodyScanResult(
+ bodyTypes,
+ castTypes,
+ metaDataTypes,
+ typeCheckTypes,
+ accessedFieldMembers
+ );
}
- [NotNull]
- internal static IEnumerable> GetCastTypes(
- this MethodDefinition methodDefinition,
- TypeFactory typeFactory
- )
+ ///
+ /// Comparer that deduplicates FieldReference by full name, avoiding repeated field lookups.
+ ///
+ private sealed class FieldReferenceNameComparer : IEqualityComparer
{
- var instructions =
- methodDefinition.Body?.Instructions ?? Enumerable.Empty();
-
- return instructions
- .Where(inst => inst.OpCode == OpCodes.Castclass && inst.Operand is TypeReference)
- .Select(inst =>
- typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- (TypeReference)inst.Operand
- )
- );
- }
+ internal static readonly FieldReferenceNameComparer Instance =
+ new FieldReferenceNameComparer();
- [NotNull]
- internal static IEnumerable> GetMetaDataTypes(
- this MethodDefinition methodDefinition,
- TypeFactory typeFactory
- )
- {
- var instructions =
- methodDefinition.Body?.Instructions ?? Enumerable.Empty();
-
- return instructions
- .Where(inst => inst.OpCode == OpCodes.Ldtoken && inst.Operand is TypeReference)
- .Select(inst =>
- typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- (TypeReference)inst.Operand
- )
- );
- }
+ public bool Equals(FieldReference x, FieldReference y)
+ {
+ if (x == null && y == null)
+ return true;
+ if (x == null || y == null)
+ return false;
+ return x.FullName == y.FullName;
+ }
- [NotNull]
- internal static IEnumerable> GetTypeCheckTypes(
- this MethodDefinition methodDefinition,
- TypeFactory typeFactory
- )
- {
- var instructions =
- methodDefinition.Body?.Instructions ?? Enumerable.Empty();
-
- return instructions
- .Where(inst => inst.OpCode == OpCodes.Isinst && inst.Operand is TypeReference)
- .Select(inst =>
- typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- (TypeReference)inst.Operand
- )
- );
+ public int GetHashCode(FieldReference obj)
+ {
+ return obj?.FullName?.GetHashCode() ?? 0;
+ }
}
internal static bool IsIterator(this MethodDefinition methodDefinition)
@@ -216,52 +289,6 @@ internal static bool IsAsync(this MethodDefinition methodDefinition)
);
}
- [NotNull]
- internal static IEnumerable GetAccessedFieldMembers(
- this MethodDefinition methodDefinition,
- TypeFactory typeFactory
- )
- {
- var accessedFieldMembers = new List();
- var instructions =
- methodDefinition.Body?.Instructions.ToList() ?? new List();
- var accessedFieldReferences = instructions
- .Select(inst => inst.Operand)
- .OfType()
- .Distinct();
-
- foreach (var fieldReference in accessedFieldReferences)
- {
- var declaringType = typeFactory.GetOrCreateStubTypeInstanceFromTypeReference(
- fieldReference.DeclaringType
- );
- var matchingFieldMembers = declaringType
- .Type.GetFieldMembers()
- .Where(member => member.Name == fieldReference.Name)
- .ToList();
-
- switch (matchingFieldMembers.Count)
- {
- case 0:
- var stubFieldMember = typeFactory.CreateStubFieldMemberFromFieldReference(
- declaringType.Type,
- fieldReference
- );
- accessedFieldMembers.Add(stubFieldMember);
- break;
- case 1:
- accessedFieldMembers.Add(matchingFieldMembers.First());
- break;
- default:
- throw new MultipleOccurrencesInSequenceException(
- $"Multiple Fields matching {fieldReference.FullName} found in provided type."
- );
- }
- }
-
- return accessedFieldMembers.Distinct();
- }
-
internal static bool IsCompilerGenerated(this MemberReference memberReference)
{
if (memberReference.Name.HasCompilerGeneratedName())
diff --git a/ArchUnitNET/Loader/NamespaceRegistry.cs b/ArchUnitNET/Loader/NamespaceRegistry.cs
deleted file mode 100644
index c2a39bb5b..000000000
--- a/ArchUnitNET/Loader/NamespaceRegistry.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Collections.Generic;
-using ArchUnitNET.Domain;
-
-namespace ArchUnitNET.Loader
-{
- internal class NamespaceRegistry
- {
- private readonly Dictionary _namespaces =
- new Dictionary();
-
- public IEnumerable Namespaces => _namespaces.Values;
-
- public Namespace GetOrCreateNamespace(string typeNamespaceName)
- {
- return RegistryUtils.GetFromDictOrCreateAndAdd(
- typeNamespaceName,
- _namespaces,
- s => new Namespace(typeNamespaceName, new List())
- );
- }
- }
-}
diff --git a/ArchUnitNET/Loader/RegexUtils.cs b/ArchUnitNET/Loader/RegexUtils.cs
deleted file mode 100644
index b9503a615..000000000
--- a/ArchUnitNET/Loader/RegexUtils.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Text.RegularExpressions;
-
-namespace ArchUnitNET.Loader
-{
- public static class RegexUtils
- {
- private static readonly Regex GetMethodPropertyMemberRegex = new Regex(@"get_(.+)\(\)");
- private static readonly Regex SetMethodPropertyMemberRegex = new Regex(@"set_(.+)\((.+)\)");
-
- public static string MatchGetPropertyName(string methodName)
- {
- var match = GetMethodPropertyMemberRegex.Match(methodName);
- if (!match.Success)
- {
- return null;
- }
-
- var accessedPropertyName = match.Groups[1].Value;
- return accessedPropertyName;
- }
-
- public static string MatchSetPropertyName(string methodName)
- {
- var match = SetMethodPropertyMemberRegex.Match(methodName);
- if (!match.Success)
- {
- return null;
- }
-
- var accessedPropertyName = match.Groups[1].Value;
- return accessedPropertyName;
- }
-
- public static bool MatchNamespaces(string goalNamespace, string currentNamespace)
- {
- return goalNamespace == null || currentNamespace.StartsWith(goalNamespace);
- }
- }
-}
diff --git a/ArchUnitNET/Loader/RegistryUtils.cs b/ArchUnitNET/Loader/RegistryUtils.cs
deleted file mode 100644
index f31afa94c..000000000
--- a/ArchUnitNET/Loader/RegistryUtils.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace ArchUnitNET.Loader
-{
- internal static class RegistryUtils
- {
- public static T GetFromDictOrCreateAndAdd(
- TK key,
- Dictionary dict,
- Func createFunc
- )
- {
- if (dict.TryGetValue(key, out var value))
- {
- return value;
- }
-
- value = createFunc(key);
- dict.Add(key, value);
-
- return value;
- }
- }
-}
diff --git a/ArchUnitNET/Loader/TypeProcessor.cs b/ArchUnitNET/Loader/TypeProcessor.cs
new file mode 100644
index 000000000..e04e658b1
--- /dev/null
+++ b/ArchUnitNET/Loader/TypeProcessor.cs
@@ -0,0 +1,1138 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using ArchUnitNET.Domain;
+using ArchUnitNET.Domain.Dependencies;
+using ArchUnitNET.Domain.Extensions;
+using JetBrains.Annotations;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace ArchUnitNET.Loader
+{
+ ///
+ /// Contains all type-processing phases that populate the domain model with members,
+ /// dependencies, and attributes. Each public method corresponds to a numbered phase
+ /// and must be called in the required order (see ).
+ ///
+ internal static class TypeProcessor
+ {
+ // ── Phase 1: Base class dependency ──────────────────────────────────
+
+ ///
+ /// If the type has a base class, creates an
+ /// and adds it to the type's dependency list. Skipped for interfaces and types
+ /// whose base is not a .
+ ///
+ internal static void AddBaseClassDependency(
+ IType type,
+ TypeDefinition typeDefinition,
+ DomainResolver domainResolver
+ )
+ {
+ var baseTypeRef = typeDefinition.BaseType;
+ if (baseTypeRef == null)
+ {
+ return;
+ }
+
+ var baseType = domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(baseTypeRef);
+ if (!(baseType.Type is Class baseClass))
+ {
+ return;
+ }
+
+ var dependency = new InheritsBaseClassDependency(
+ type,
+ new TypeInstance(
+ baseClass,
+ baseType.GenericArguments,
+ baseType.ArrayDimensions
+ )
+ );
+ type.Dependencies.Add(dependency);
+ }
+
+ // ── Phase 2: Members ────────────────────────────────────────────────
+
+ ///
+ /// Creates field, property, and method members from the Cecil
+ /// and adds them to the domain type.
+ /// Returns a containing method pairs and
+ /// a mapping from getter/setter s to
+ /// their s.
+ ///
+ internal static MemberData AddMembers(
+ ITypeInstance typeInstance,
+ TypeDefinition typeDefinition,
+ DomainResolver domainResolver
+ )
+ {
+ var methodPairs = new List<(MethodMember Member, MethodDefinition Definition)>();
+ var propertyByAccessor = new Dictionary();
+ var members = CreateMembers(
+ typeInstance,
+ typeDefinition,
+ domainResolver,
+ methodPairs,
+ propertyByAccessor
+ );
+ typeInstance.Type.Members.AddRange(members);
+ return new MemberData(methodPairs, propertyByAccessor);
+ }
+
+ [NotNull]
+ private static IEnumerable CreateMembers(
+ ITypeInstance typeInstance,
+ [NotNull] TypeDefinition typeDefinition,
+ DomainResolver domainResolver,
+ List<(MethodMember Member, MethodDefinition Definition)> methodPairs,
+ Dictionary propertyByAccessor
+ )
+ {
+ var fieldMembers = typeDefinition
+ .Fields.Where(fieldDefinition => !fieldDefinition.IsBackingField())
+ .Select(fieldDef =>
+ (IMember)domainResolver.GetOrCreateFieldMember(typeInstance.Type, fieldDef)
+ );
+
+ var propertyMembers = typeDefinition.Properties.Select(propDef =>
+ {
+ var propertyMember = CreatePropertyMember(typeInstance, propDef, domainResolver);
+ if (propDef.GetMethod != null)
+ {
+ propertyByAccessor[propDef.GetMethod] = propertyMember;
+ }
+
+ if (propDef.SetMethod != null)
+ {
+ propertyByAccessor[propDef.SetMethod] = propertyMember;
+ }
+
+ return (IMember)propertyMember;
+ });
+
+ var methodMembers = typeDefinition.Methods.Select(method =>
+ {
+ var member = domainResolver
+ .GetOrCreateMethodMemberFromMethodReference(typeInstance, method)
+ .Member;
+ methodPairs.Add((member, method));
+ return (IMember)member;
+ });
+
+ return fieldMembers
+ .Concat(propertyMembers)
+ .Concat(methodMembers)
+ .Where(member => !member.IsCompilerGenerated);
+ }
+
+ [NotNull]
+ private static PropertyMember CreatePropertyMember(
+ ITypeInstance typeInstance,
+ PropertyDefinition propertyDefinition,
+ DomainResolver domainResolver
+ )
+ {
+ var typeReference = propertyDefinition.PropertyType;
+ var propertyType = domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ typeReference
+ );
+ var isCompilerGenerated = propertyDefinition.IsCompilerGenerated();
+ var isStatic =
+ (propertyDefinition.SetMethod != null && propertyDefinition.SetMethod.IsStatic)
+ || (propertyDefinition.GetMethod != null && propertyDefinition.GetMethod.IsStatic);
+ var writeAccessor = GetPropertyWriteAccessor(propertyDefinition);
+ return new PropertyMember(
+ typeInstance.Type,
+ propertyDefinition.Name,
+ propertyDefinition.FullName,
+ propertyType,
+ isCompilerGenerated,
+ isStatic,
+ writeAccessor
+ );
+ }
+
+ private static Writability GetPropertyWriteAccessor(
+ [NotNull] PropertyDefinition propertyDefinition
+ )
+ {
+ if (propertyDefinition.SetMethod == null)
+ {
+ return Writability.ReadOnly;
+ }
+
+ if (CheckPropertyHasInitSetterInNetStandardCompatibleWay(propertyDefinition))
+ {
+ return Writability.InitOnly;
+ }
+
+ return Writability.Writable;
+ }
+
+ private static bool CheckPropertyHasInitSetterInNetStandardCompatibleWay(
+ PropertyDefinition propertyDefinition
+ )
+ {
+ return propertyDefinition.SetMethod?.ReturnType.IsRequiredModifier == true
+ && ((RequiredModifierType)propertyDefinition.SetMethod.ReturnType)
+ .ModifierType
+ .FullName == "System.Runtime.CompilerServices.IsExternalInit";
+ }
+
+ // ── Phase 3: Generic parameter dependencies ─────────────────────────
+
+ ///
+ /// Assigns declarers to generic parameters and creates type-constraint dependencies
+ /// for both type-level and member-level generic parameters.
+ ///
+ internal static void AddGenericParameterDependencies(IType type)
+ {
+ // Type-level generic parameters
+ foreach (var genericParameter in type.GenericParameters)
+ {
+ genericParameter.AssignDeclarer(type);
+ foreach (var typeInstanceConstraint in genericParameter.TypeInstanceConstraints)
+ {
+ var dependency = new TypeGenericParameterTypeConstraintDependency(
+ genericParameter,
+ typeInstanceConstraint
+ );
+ genericParameter.Dependencies.Add(dependency);
+ }
+ }
+
+ // Member-level generic parameters
+ foreach (var member in type.Members)
+ {
+ foreach (var genericParameter in member.GenericParameters)
+ {
+ genericParameter.AssignDeclarer(member);
+ foreach (var typeInstanceConstraint in genericParameter.TypeInstanceConstraints)
+ {
+ var dependency = new MemberGenericParameterTypeConstraintDependency(
+ genericParameter,
+ typeInstanceConstraint
+ );
+ genericParameter.Dependencies.Add(dependency);
+ }
+ }
+ }
+ }
+
+ // ── Phase 4: Attributes and attribute dependencies ──────────────────
+
+ ///
+ /// Creates attribute instances from Cecil custom attributes on the type, its generic
+ /// parameters, and its members, then adds corresponding attribute-type dependencies.
+ ///
+ internal static void AddAttributesAndAttributeDependencies(
+ IType type,
+ TypeDefinition typeDefinition,
+ DomainResolver domainResolver,
+ IReadOnlyList<(MethodMember Member, MethodDefinition Definition)> methodPairs
+ )
+ {
+ typeDefinition.CustomAttributes.ForEach(attr =>
+ AddAttributeArgumentReferenceDependencies(type, attr, domainResolver)
+ );
+ var typeAttributeInstances = CreateAttributesFromCustomAttributes(
+ typeDefinition.CustomAttributes,
+ domainResolver
+ )
+ .ToList();
+ type.AttributeInstances.AddRange(typeAttributeInstances);
+ var typeAttributeDependencies = typeAttributeInstances.Select(
+ attributeInstance => new AttributeTypeDependency(type, attributeInstance)
+ );
+ type.Dependencies.AddRange(typeAttributeDependencies);
+
+ SetUpAttributesForTypeGenericParameters(type, typeDefinition, domainResolver);
+ CollectAttributesForMembers(type, typeDefinition, domainResolver, methodPairs);
+ }
+
+ private static void SetUpAttributesForTypeGenericParameters(
+ IType type,
+ TypeDefinition typeDefinition,
+ DomainResolver domainResolver
+ )
+ {
+ foreach (var genericParameter in typeDefinition.GenericParameters)
+ {
+ var param = type.GenericParameters.First(parameter =>
+ parameter.Name == genericParameter.Name
+ );
+ var attributeInstances = CreateAttributesFromCustomAttributes(
+ genericParameter.CustomAttributes,
+ domainResolver
+ )
+ .ToList();
+ type.AttributeInstances.AddRange(attributeInstances);
+ param.AttributeInstances.AddRange(attributeInstances);
+ var genericParameterAttributeDependencies = attributeInstances.Select(
+ attributeInstance => new AttributeTypeDependency(type, attributeInstance)
+ );
+ type.Dependencies.AddRange(genericParameterAttributeDependencies);
+ }
+ }
+
+ private static void CollectAttributesForMembers(
+ IType type,
+ TypeDefinition typeDefinition,
+ DomainResolver domainResolver,
+ IReadOnlyList<(MethodMember Member, MethodDefinition Definition)> methodPairs
+ )
+ {
+ typeDefinition
+ .Fields.Where(x => !x.IsBackingField() && !x.IsCompilerGenerated())
+ .ForEach(fieldDef => SetUpAttributesForField(type, fieldDef, domainResolver));
+
+ typeDefinition
+ .Properties.Where(x => !x.IsCompilerGenerated())
+ .ForEach(propDef => SetUpAttributesForProperty(type, propDef, domainResolver));
+
+ // Build a lookup from method full-name -> MethodMember to avoid O(n) scans
+ var methodMemberByFullName = new Dictionary(methodPairs.Count);
+ foreach (var pair in methodPairs)
+ {
+ var key = pair.Definition.BuildFullName();
+ if (!methodMemberByFullName.ContainsKey(key))
+ {
+ methodMemberByFullName[key] = pair.Member;
+ }
+ }
+
+ typeDefinition
+ .Methods.Where(x => !x.IsCompilerGenerated())
+ .ForEach(methodDef =>
+ {
+ MethodMember methodMember;
+ if (
+ !methodMemberByFullName.TryGetValue(
+ methodDef.BuildFullName(),
+ out methodMember
+ )
+ )
+ {
+ return;
+ }
+
+ SetUpAttributesForMethod(methodDef, methodMember, type, domainResolver);
+ });
+ }
+
+ private static void SetUpAttributesForField(
+ IType type,
+ FieldDefinition fieldDefinition,
+ DomainResolver domainResolver
+ )
+ {
+ var fieldMember = type.GetFieldMembers()
+ .WhereFullNameIs(fieldDefinition.FullName)
+ .RequiredNotNull();
+ CollectMemberAttributesAndDependencies(
+ type,
+ fieldMember,
+ fieldDefinition.CustomAttributes.ToList(),
+ fieldMember.MemberDependencies,
+ domainResolver
+ );
+ }
+
+ private static void SetUpAttributesForProperty(
+ IType type,
+ PropertyDefinition propertyDefinition,
+ DomainResolver domainResolver
+ )
+ {
+ var propertyMember = type.GetPropertyMembers()
+ .WhereFullNameIs(propertyDefinition.FullName)
+ .RequiredNotNull();
+ CollectMemberAttributesAndDependencies(
+ type,
+ propertyMember,
+ propertyDefinition.CustomAttributes.ToList(),
+ propertyMember.AttributeDependencies,
+ domainResolver
+ );
+ }
+
+ private static void SetUpAttributesForMethod(
+ MethodDefinition methodDefinition,
+ MethodMember methodMember,
+ IType type,
+ DomainResolver domainResolver
+ )
+ {
+ var memberCustomAttributes = methodDefinition.GetAllMethodCustomAttributes().ToList();
+ SetUpAttributesForMethodGenericParameters(
+ methodDefinition,
+ methodMember,
+ domainResolver
+ );
+ CollectMemberAttributesAndDependencies(
+ type,
+ methodMember,
+ memberCustomAttributes,
+ methodMember.MemberDependencies,
+ domainResolver
+ );
+ }
+
+ private static void SetUpAttributesForMethodGenericParameters(
+ MethodDefinition methodDefinition,
+ MethodMember methodMember,
+ DomainResolver domainResolver
+ )
+ {
+ foreach (var genericParameter in methodDefinition.GenericParameters)
+ {
+ var param = methodMember.GenericParameters.First(parameter =>
+ parameter.Name == genericParameter.Name
+ );
+ var customAttributes = genericParameter.CustomAttributes;
+ customAttributes.ForEach(attr =>
+ AddAttributeArgumentReferenceDependencies(
+ methodMember.DeclaringType,
+ attr,
+ domainResolver
+ )
+ );
+ var attributeInstances = CreateAttributesFromCustomAttributes(
+ customAttributes,
+ domainResolver
+ )
+ .ToList();
+ methodMember.AttributeInstances.AddRange(attributeInstances);
+ param.AttributeInstances.AddRange(attributeInstances);
+ var genericParameterAttributeDependencies = attributeInstances.Select(
+ attributeInstance => new AttributeMemberDependency(
+ methodMember,
+ attributeInstance
+ )
+ );
+ methodMember.MemberDependencies.AddRange(genericParameterAttributeDependencies);
+ }
+ }
+
+ private static void CollectMemberAttributesAndDependencies(
+ IType type,
+ IMember member,
+ List memberCustomAttributes,
+ List attributeDependencies,
+ DomainResolver domainResolver
+ )
+ {
+ memberCustomAttributes.ForEach(attr =>
+ AddAttributeArgumentReferenceDependencies(type, attr, domainResolver)
+ );
+ var memberAttributeInstances = CreateAttributesFromCustomAttributes(
+ memberCustomAttributes,
+ domainResolver
+ )
+ .ToList();
+ member.AttributeInstances.AddRange(memberAttributeInstances);
+ var methodAttributeDependencies = memberAttributeInstances.Select(
+ attributeInstance => new AttributeMemberDependency(member, attributeInstance)
+ );
+ attributeDependencies.AddRange(methodAttributeDependencies);
+ }
+
+ [NotNull]
+ private static IEnumerable CreateAttributesFromCustomAttributes(
+ IEnumerable customAttributes,
+ DomainResolver domainResolver
+ )
+ {
+ return customAttributes
+ .Where(customAttribute =>
+ customAttribute.AttributeType.FullName
+ != "Microsoft.CodeAnalysis.EmbeddedAttribute"
+ && customAttribute.AttributeType.FullName
+ != "System.Runtime.CompilerServices.NullableAttribute"
+ && customAttribute.AttributeType.FullName
+ != "System.Runtime.CompilerServices.NullableContextAttribute"
+ )
+ .Select(attr => attr.CreateAttributeFromCustomAttribute(domainResolver));
+ }
+
+ private static void AddAttributeArgumentReferenceDependencies(
+ IType type,
+ ICustomAttribute customAttribute,
+ DomainResolver domainResolver
+ )
+ {
+ if (!customAttribute.HasConstructorArguments)
+ {
+ return;
+ }
+
+ var attributeConstructorArgs = customAttribute.ConstructorArguments;
+ attributeConstructorArgs
+ .Where(attributeArgument =>
+ attributeArgument.Value is TypeReference typeReference
+ && !typeReference.IsCompilerGenerated()
+ )
+ .Select(attributeArgument =>
+ (typeReference: attributeArgument.Value as TypeReference, attributeArgument)
+ )
+ .ForEach(tuple =>
+ {
+ var argumentType = domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ tuple.typeReference
+ );
+ var dependency = new TypeReferenceDependency(type, argumentType);
+ type.Dependencies.Add(dependency);
+ });
+ }
+
+ // ── Phase 5: Assembly-level attributes ──────────────────────────────
+
+ ///
+ /// Creates attribute instances from the assembly-level custom attributes of the
+ /// given and adds them to the domain
+ /// .
+ ///
+ internal static void CollectAssemblyAttributes(
+ Assembly assembly,
+ AssemblyDefinition assemblyDefinition,
+ DomainResolver domainResolver
+ )
+ {
+ var attributeInstances = assemblyDefinition
+ .CustomAttributes.Select(attr =>
+ attr.CreateAttributeFromCustomAttribute(domainResolver)
+ )
+ .ToList();
+ assembly.AttributeInstances.AddRange(attributeInstances);
+ }
+
+ // ── Phase 6: Field and property type dependencies ───────────────────
+
+ ///
+ /// Creates and
+ /// instances for each field and property member of the type.
+ ///
+ internal static void AddFieldAndPropertyDependencies(IType type)
+ {
+ type.GetFieldMembers()
+ .ForEach(field =>
+ {
+ var dependency = new FieldTypeDependency(field);
+ if (!field.MemberDependencies.Contains(dependency))
+ {
+ field.MemberDependencies.Add(dependency);
+ }
+ });
+
+ type.GetPropertyMembers()
+ .ForEach(property =>
+ {
+ var dependency = new PropertyTypeDependency(property);
+ if (!property.MemberDependencies.Contains(dependency))
+ {
+ property.MemberDependencies.Add(dependency);
+ }
+ });
+ }
+
+ // ── Phase 7: Method signature and body dependencies ─────────────────
+
+ ///
+ /// Resolves method signature and body dependencies for each method in the type,
+ /// including method calls, body types, cast types, type checks, metadata types,
+ /// and accessed fields. Also links getter/setter methods to their properties.
+ ///
+ internal static void AddMethodDependencies(
+ IType type,
+ DomainResolver domainResolver,
+ IReadOnlyList<(MethodMember Member, MethodDefinition Definition)> methodPairs,
+ IReadOnlyDictionary propertyByAccessor
+ )
+ {
+ methodPairs
+ .Where(pair => pair.Member != null && !pair.Member.IsCompilerGenerated)
+ .Select(pair =>
+ {
+ var dependencies = CreateMethodSignatureDependencies(
+ pair.Definition,
+ pair.Member,
+ domainResolver
+ )
+ .Concat(
+ CreateMethodBodyDependencies(
+ type,
+ pair.Definition,
+ pair.Member,
+ domainResolver
+ )
+ );
+ if (pair.Definition.IsSetter || pair.Definition.IsGetter)
+ {
+ AssignDependenciesToProperty(
+ pair.Member,
+ pair.Definition,
+ propertyByAccessor
+ );
+ }
+
+ return (pair.Member, dependencies);
+ })
+ .ForEach(tuple =>
+ {
+ var (methodMember, dependencies) = tuple;
+ methodMember.MemberDependencies.AddRange(dependencies);
+ });
+ }
+
+ private static void AssignDependenciesToProperty(
+ MethodMember methodMember,
+ MethodDefinition methodDefinition,
+ IReadOnlyDictionary propertyByAccessor
+ )
+ {
+ if (!propertyByAccessor.TryGetValue(methodDefinition, out var accessedProperty))
+ {
+ return;
+ }
+
+ accessedProperty.IsVirtual |= methodMember.IsVirtual;
+
+ var methodForm = methodDefinition.GetMethodForm();
+ switch (methodForm)
+ {
+ case MethodForm.Getter:
+ accessedProperty.Getter = methodMember;
+ break;
+ case MethodForm.Setter:
+ accessedProperty.Setter = methodMember;
+ break;
+ }
+
+ var methodBody = methodDefinition.Body;
+
+ if (methodBody == null)
+ {
+ return;
+ }
+
+ if (
+ !methodBody
+ .Instructions.Select(instruction => instruction.Operand)
+ .OfType()
+ .Any(definition => definition.IsBackingField())
+ )
+ {
+ accessedProperty.IsAutoProperty = false;
+ }
+ }
+
+ [NotNull]
+ private static IEnumerable CreateMethodSignatureDependencies(
+ MethodReference methodReference,
+ MethodMember methodMember,
+ DomainResolver domainResolver
+ )
+ {
+ var returnType = methodReference.GetReturnType(domainResolver);
+ return (returnType != null ? new[] { returnType } : Array.Empty>())
+ .Concat(methodReference.GetParameters(domainResolver))
+ .Concat(methodReference.GetGenericParameters(domainResolver))
+ .Distinct()
+ .Select(signatureType => new MethodSignatureDependency(
+ methodMember,
+ signatureType
+ ));
+ }
+
+ [NotNull]
+ private static IEnumerable CreateMethodBodyDependencies(
+ IType type,
+ MethodDefinition methodDefinition,
+ MethodMember methodMember,
+ DomainResolver domainResolver
+ )
+ {
+ var methodBody = methodDefinition.Body;
+ if (methodBody == null)
+ {
+ yield break;
+ }
+
+ var visitedMethodReferences = new List { methodDefinition };
+ var bodyTypes = new List>();
+
+ if (methodDefinition.IsAsync())
+ {
+ HandleAsync(
+ out methodDefinition,
+ ref methodBody,
+ bodyTypes,
+ visitedMethodReferences,
+ domainResolver
+ );
+ }
+
+ if (methodDefinition.IsIterator())
+ {
+ HandleIterator(
+ out methodDefinition,
+ ref methodBody,
+ bodyTypes,
+ visitedMethodReferences,
+ domainResolver
+ );
+ }
+
+ var scan = methodDefinition.ScanMethodBody(domainResolver);
+ bodyTypes.AddRange(scan.BodyTypes);
+
+ var castTypes = scan.CastTypes;
+
+ var typeCheckTypes = scan.TypeCheckTypes;
+
+ var metaDataTypes = scan.MetaDataTypes;
+
+ var accessedFieldMembers = scan.AccessedFieldMembers;
+
+ var calledMethodMembers = CreateMethodBodyDependenciesRecursive(
+ methodBody,
+ visitedMethodReferences,
+ bodyTypes,
+ castTypes,
+ typeCheckTypes,
+ metaDataTypes,
+ accessedFieldMembers,
+ domainResolver
+ );
+
+ foreach (
+ var calledMethodMember in calledMethodMembers
+ .Where(method => !method.Member.IsCompilerGenerated)
+ .Distinct()
+ )
+ {
+ yield return new MethodCallDependency(methodMember, calledMethodMember);
+ }
+
+ foreach (
+ var bodyType in bodyTypes
+ .Where(instance => !instance.Type.IsCompilerGenerated)
+ .Distinct()
+ )
+ {
+ yield return new BodyTypeMemberDependency(methodMember, bodyType);
+ }
+
+ foreach (
+ var castType in castTypes
+ .Where(instance => !instance.Type.IsCompilerGenerated)
+ .Distinct()
+ )
+ {
+ yield return new CastTypeDependency(methodMember, castType);
+ }
+
+ foreach (
+ var typeCheckType in typeCheckTypes
+ .Where(instance => !instance.Type.IsCompilerGenerated)
+ .Distinct()
+ )
+ {
+ yield return new TypeCheckDependency(methodMember, typeCheckType);
+ }
+
+ foreach (
+ var metaDataType in metaDataTypes
+ .Where(instance => !instance.Type.IsCompilerGenerated)
+ .Distinct()
+ )
+ {
+ yield return new MetaDataDependency(methodMember, metaDataType);
+ }
+
+ foreach (
+ var fieldMember in accessedFieldMembers
+ .Where(field => !field.IsCompilerGenerated)
+ .Distinct()
+ )
+ {
+ yield return new AccessFieldDependency(methodMember, fieldMember);
+ }
+ }
+
+ private static IEnumerable CreateMethodBodyDependenciesRecursive(
+ MethodBody methodBody,
+ ICollection visitedMethodReferences,
+ List> bodyTypes,
+ List> castTypes,
+ List> typeCheckTypes,
+ List> metaDataTypes,
+ List accessedFieldMembers,
+ DomainResolver domainResolver
+ )
+ {
+ var calledMethodReferences = methodBody
+ .Instructions.Select(instruction => instruction.Operand)
+ .OfType();
+
+ foreach (
+ var calledMethodReference in calledMethodReferences.Except(visitedMethodReferences)
+ )
+ {
+ visitedMethodReferences.Add(calledMethodReference);
+
+ var calledType = domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ calledMethodReference.DeclaringType
+ );
+ var calledMethodMember = domainResolver.GetOrCreateMethodMemberFromMethodReference(
+ calledType,
+ calledMethodReference
+ );
+
+ bodyTypes.AddRange(calledMethodMember.MemberGenericArguments);
+
+ if (calledMethodReference.IsCompilerGenerated())
+ {
+ MethodDefinition calledMethodDefinition;
+ try
+ {
+ calledMethodDefinition = calledMethodReference.Resolve();
+ }
+ catch (AssemblyResolutionException)
+ {
+ calledMethodDefinition = null;
+ }
+
+ if (calledMethodDefinition?.Body == null)
+ {
+ //MethodReference to compiler generated type not resolvable, skip
+ continue;
+ }
+
+ var calledMethodBody = calledMethodDefinition.Body;
+
+ if (calledMethodDefinition.IsIterator())
+ {
+ HandleIterator(
+ out calledMethodDefinition,
+ ref calledMethodBody,
+ bodyTypes,
+ visitedMethodReferences,
+ domainResolver
+ );
+ }
+
+ var calledScan = calledMethodDefinition.ScanMethodBody(domainResolver);
+ bodyTypes.AddRange(calledScan.BodyTypes);
+ castTypes.AddRange(calledScan.CastTypes);
+ typeCheckTypes.AddRange(calledScan.TypeCheckTypes);
+ metaDataTypes.AddRange(calledScan.MetaDataTypes);
+ accessedFieldMembers.AddRange(calledScan.AccessedFieldMembers);
+
+ foreach (
+ var dep in CreateMethodBodyDependenciesRecursive(
+ calledMethodBody,
+ visitedMethodReferences,
+ bodyTypes,
+ castTypes,
+ typeCheckTypes,
+ metaDataTypes,
+ accessedFieldMembers,
+ domainResolver
+ )
+ )
+ {
+ yield return dep;
+ }
+ }
+ else
+ {
+ yield return calledMethodMember;
+ }
+ }
+ }
+
+ private static void HandleIterator(
+ out MethodDefinition methodDefinition,
+ ref MethodBody methodBody,
+ List> bodyTypes,
+ ICollection visitedMethodReferences,
+ DomainResolver domainResolver
+ )
+ {
+ var compilerGeneratedGeneratorObject = (
+ (MethodReference)methodBody.Instructions.First(inst => inst.IsNewObjectOp()).Operand
+ ).DeclaringType.Resolve();
+ methodDefinition = compilerGeneratedGeneratorObject.Methods.First(method =>
+ method.Name == nameof(IEnumerator.MoveNext)
+ );
+ visitedMethodReferences.Add(methodDefinition);
+ methodBody = methodDefinition.Body;
+
+ var fieldsExceptGeneratorStateInfo = compilerGeneratedGeneratorObject.Fields.Where(
+ field =>
+ !(
+ field.Name.EndsWith("__state")
+ || field.Name.EndsWith("__current")
+ || field.Name.EndsWith("__initialThreadId")
+ || field.Name.EndsWith("__this")
+ )
+ );
+
+ bodyTypes.AddRange(
+ fieldsExceptGeneratorStateInfo.Select(bodyField =>
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(bodyField.FieldType)
+ )
+ );
+ }
+
+ private static void HandleAsync(
+ out MethodDefinition methodDefinition,
+ ref MethodBody methodBody,
+ List> bodyTypes,
+ ICollection visitedMethodReferences,
+ DomainResolver domainResolver
+ )
+ {
+ var compilerGeneratedGeneratorObject = (
+ (MethodReference)
+ methodBody.Instructions.FirstOrDefault(inst => inst.IsNewObjectOp())?.Operand
+ )?.DeclaringType.Resolve();
+
+ if (compilerGeneratedGeneratorObject == null)
+ {
+ methodDefinition = methodBody.Method;
+ return;
+ }
+
+ methodDefinition = compilerGeneratedGeneratorObject.Methods.First(method =>
+ method.Name == nameof(IAsyncStateMachine.MoveNext)
+ );
+
+ visitedMethodReferences.Add(methodDefinition);
+ methodBody = methodDefinition.Body;
+
+ var fieldsExceptGeneratorStateInfo = compilerGeneratedGeneratorObject
+ .Fields.Where(field =>
+ !(
+ field.Name.EndsWith("__state")
+ || field.Name.EndsWith("__builder")
+ || field.Name.EndsWith("__this")
+ )
+ )
+ .ToArray();
+
+ bodyTypes.AddRange(
+ fieldsExceptGeneratorStateInfo.Select(bodyField =>
+ domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(bodyField.FieldType)
+ )
+ );
+ }
+
+ // ── Phase 8: Generic argument dependencies ──────────────────────────
+
+ ///
+ /// Propagates generic-parameter dependencies to the owning type and member,
+ /// then recursively discovers nested generic-argument dependencies in all
+ /// existing type and member dependencies.
+ ///
+ internal static void AddGenericArgumentDependencies(IType type)
+ {
+ // Type-level generic argument dependencies
+ foreach (var parameter in type.GenericParameters)
+ {
+ type.Dependencies.AddRange(parameter.Dependencies);
+ }
+
+ // Member-level generic argument dependencies
+ foreach (var member in type.Members)
+ {
+ foreach (var parameter in member.GenericParameters)
+ {
+ member.MemberDependencies.AddRange(
+ parameter.Dependencies.Cast()
+ );
+ }
+ }
+
+ // Recursive generic arguments in type dependencies
+ var typeDependencies = new List();
+ foreach (var dependency in type.Dependencies)
+ {
+ FindGenericArgumentsInTypeDependenciesRecursive(
+ type,
+ dependency.TargetGenericArguments,
+ typeDependencies
+ );
+ }
+
+ type.Dependencies.AddRange(typeDependencies);
+
+ // Recursive generic arguments in member dependencies
+ foreach (var member in type.Members)
+ {
+ var memberDependencies = new List();
+ foreach (var dependency in member.Dependencies)
+ {
+ FindGenericArgumentsInMemberDependenciesRecursive(
+ member,
+ dependency.TargetGenericArguments,
+ memberDependencies
+ );
+ }
+
+ member.MemberDependencies.AddRange(memberDependencies);
+ }
+ }
+
+ private static void FindGenericArgumentsInTypeDependenciesRecursive(
+ IType type,
+ IEnumerable targetGenericArguments,
+ ICollection createdDependencies
+ )
+ {
+ foreach (
+ var genericArgument in targetGenericArguments.Where(argument =>
+ !argument.Type.IsGenericParameter
+ )
+ )
+ {
+ createdDependencies.Add(new GenericArgumentTypeDependency(type, genericArgument));
+ FindGenericArgumentsInTypeDependenciesRecursive(
+ type,
+ genericArgument.GenericArguments,
+ createdDependencies
+ );
+ }
+ }
+
+ private static void FindGenericArgumentsInMemberDependenciesRecursive(
+ IMember member,
+ IEnumerable targetGenericArguments,
+ ICollection createdDependencies
+ )
+ {
+ foreach (
+ var genericArgument in targetGenericArguments.Where(argument =>
+ !argument.Type.IsGenericParameter
+ )
+ )
+ {
+ createdDependencies.Add(
+ new GenericArgumentMemberDependency(member, genericArgument)
+ );
+ FindGenericArgumentsInMemberDependenciesRecursive(
+ member,
+ genericArgument.GenericArguments,
+ createdDependencies
+ );
+ }
+ }
+
+ // ── Phase 9: Interface and member-to-type dependencies ──────────────
+
+ ///
+ /// Creates for each interface the type
+ /// implements (including inherited interfaces), then rolls up all member-level
+ /// dependencies to the type's dependency list.
+ ///
+ internal static void AddClassDependencies(
+ IType type,
+ TypeDefinition typeDefinition,
+ DomainResolver domainResolver
+ )
+ {
+ // Interface dependencies
+ GetInterfacesImplementedByClass(typeDefinition)
+ .ForEach(target =>
+ {
+ var targetType = domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
+ target
+ );
+ type.Dependencies.Add(new ImplementsInterfaceDependency(type, targetType));
+ });
+
+ // Member dependencies rolled up to type level
+ type.Members.ForEach(member =>
+ {
+ type.Dependencies.AddRange(member.Dependencies);
+ });
+ }
+
+ private static IEnumerable GetInterfacesImplementedByClass(
+ TypeDefinition typeDefinition
+ )
+ {
+ var baseType = typeDefinition.BaseType?.Resolve();
+ var baseInterfaces =
+ baseType != null
+ ? GetInterfacesImplementedByClass(baseType)
+ : new List();
+
+ return typeDefinition
+ .Interfaces.Select(implementation => implementation.InterfaceType)
+ .Concat(baseInterfaces);
+ }
+
+ // ── Phase 10: Backwards dependencies ────────────────────────────────
+
+ ///
+ /// Registers each of the type's dependencies as a backwards dependency on the
+ /// target type, and each member-member dependency as a backwards dependency on
+ /// the target member.
+ ///
+ internal static void AddBackwardsDependencies(IType type)
+ {
+ type.Dependencies.ForEach(dependency =>
+ dependency.Target.BackwardsDependencies.Add(dependency)
+ );
+
+ var memberMemberDependencies = type
+ .Members.SelectMany(member => member.MemberDependencies)
+ .OfType();
+ memberMemberDependencies.ForEach(memberDependency =>
+ memberDependency.TargetMember.MemberBackwardsDependencies.Add(memberDependency)
+ );
+ }
+
+ // ── Phase 11: Register types with namespaces ────────────────────────
+
+ ///
+ /// Adds each type to its namespace's type list. Must run after all types have been
+ /// fully populated so that namespace queries return complete results.
+ ///
+ internal static void AddTypesToNamespaces(IEnumerable types)
+ {
+ foreach (var type in types)
+ {
+ ((List)type.Namespace.Types).Add(type);
+ }
+ }
+ }
+
+ ///
+ /// Bundles the results of member creation: method (MethodMember, MethodDefinition) pairs
+ /// and a mapping from getter/setter MethodDefinitions to their PropertyMembers.
+ ///
+ internal sealed class MemberData
+ {
+ public MemberData(
+ IReadOnlyList<(MethodMember Member, MethodDefinition Definition)> methodPairs,
+ IReadOnlyDictionary propertyByAccessor
+ )
+ {
+ MethodPairs = methodPairs;
+ PropertyByAccessor = propertyByAccessor;
+ }
+
+ public IReadOnlyList<(
+ MethodMember Member,
+ MethodDefinition Definition
+ )> MethodPairs { get; }
+ public IReadOnlyDictionary PropertyByAccessor { get; }
+ }
+}
diff --git a/ArchUnitNETTests/Domain/ArchitectureCacheKeyTests.cs b/ArchUnitNETTests/Domain/ArchitectureCacheKeyTests.cs
index cfae7ae60..60c99c51b 100644
--- a/ArchUnitNETTests/Domain/ArchitectureCacheKeyTests.cs
+++ b/ArchUnitNETTests/Domain/ArchitectureCacheKeyTests.cs
@@ -120,5 +120,37 @@ public void SameObjectReferenceIsSameArchitectureCacheKet()
Assert.True(_architectureCacheKey.Equals(referenceDuplicate));
}
+
+ [Fact]
+ public void KeysWithDifferentRuleEvaluationCacheSettingsAreNotEqual()
+ {
+ _architectureCacheKey.Add(_baseClassModuleName, null);
+ // cache enabled (default)
+
+ _duplicateArchitectureCacheKey.Add(_baseClassModuleName, null);
+ _duplicateArchitectureCacheKey.SetRuleEvaluationCacheDisabled();
+
+ Assert.NotEqual(_architectureCacheKey, _duplicateArchitectureCacheKey);
+ Assert.NotEqual(
+ _architectureCacheKey.GetHashCode(),
+ _duplicateArchitectureCacheKey.GetHashCode()
+ );
+ }
+
+ [Fact]
+ public void KeysBothWithCacheDisabledAreEqual()
+ {
+ _architectureCacheKey.Add(_baseClassModuleName, null);
+ _architectureCacheKey.SetRuleEvaluationCacheDisabled();
+
+ _duplicateArchitectureCacheKey.Add(_baseClassModuleName, null);
+ _duplicateArchitectureCacheKey.SetRuleEvaluationCacheDisabled();
+
+ Assert.Equal(_architectureCacheKey, _duplicateArchitectureCacheKey);
+ Assert.Equal(
+ _architectureCacheKey.GetHashCode(),
+ _duplicateArchitectureCacheKey.GetHashCode()
+ );
+ }
}
}
diff --git a/ArchUnitNETTests/Domain/Extensions/TypeExtensionTests.cs b/ArchUnitNETTests/Domain/Extensions/TypeExtensionTests.cs
index 021899dd7..e7386e52c 100644
--- a/ArchUnitNETTests/Domain/Extensions/TypeExtensionTests.cs
+++ b/ArchUnitNETTests/Domain/Extensions/TypeExtensionTests.cs
@@ -28,7 +28,7 @@ public TypeExtensionTests()
.SingleOrDefault();
_exampleAttribute = Architecture.GetClassOfType(typeof(ExampleAttribute));
- _regexUtilsTests = Architecture.GetClassOfType(typeof(RegexUtilsTest));
+ _regexUtilsTests = Architecture.GetClassOfType(typeof(BackingFieldExamples));
}
private static readonly Architecture Architecture =
diff --git a/ArchUnitNETTests/Loader/ArchLoaderTests.cs b/ArchUnitNETTests/Loader/ArchLoaderTests.cs
index 5d9408025..e7e586e6f 100644
--- a/ArchUnitNETTests/Loader/ArchLoaderTests.cs
+++ b/ArchUnitNETTests/Loader/ArchLoaderTests.cs
@@ -2,10 +2,12 @@
extern alias OtherLoaderTestAssemblyAlias;
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using ArchUnitNET.Domain;
using ArchUnitNET.Domain.Extensions;
+using ArchUnitNET.Fluent;
using ArchUnitNET.Loader;
using ArchUnitNET.xUnit;
using ArchUnitNETTests.Domain.Dependencies.Members;
@@ -20,6 +22,168 @@ namespace ArchUnitNETTests.Loader
{
public class ArchLoaderTests
{
+ [Fact]
+ public void WithoutRuleEvaluationCacheDisablesCaching()
+ {
+ // Build an architecture with caching disabled and verify that
+ // GetOrCreateObjects always invokes the providing function.
+ var architecture = new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
+ .LoadAssembly(typeof(BaseClass).Assembly)
+ .Build();
+
+ var callCount = 0;
+ var provider = new TestObjectProvider("same-description");
+
+ architecture.GetOrCreateObjects(
+ provider,
+ _ =>
+ {
+ callCount++;
+ return Enumerable.Empty();
+ }
+ );
+ architecture.GetOrCreateObjects(
+ provider,
+ _ =>
+ {
+ callCount++;
+ return Enumerable.Empty();
+ }
+ );
+
+ // Caching disabled — function should be invoked every time
+ Assert.Equal(2, callCount);
+ }
+
+ [Fact]
+ public void DefaultCacheCachesRuleEvaluationResults()
+ {
+ // Build an architecture with the default cache and verify that
+ // GetOrCreateObjects caches the result for the same provider.
+ var architecture = new ArchLoader()
+ .WithoutArchitectureCache()
+ .LoadAssembly(typeof(BaseClass).Assembly)
+ .Build();
+
+ var callCount = 0;
+ var provider = new TestObjectProvider("default-cache-test");
+
+ architecture.GetOrCreateObjects(
+ provider,
+ _ =>
+ {
+ callCount++;
+ return Enumerable.Empty();
+ }
+ );
+ architecture.GetOrCreateObjects(
+ provider,
+ _ =>
+ {
+ callCount++;
+ return Enumerable.Empty();
+ }
+ );
+
+ // Default caching — function should be invoked only once
+ Assert.Equal(1, callCount);
+ }
+
+ [Fact]
+ public void WithoutArchitectureCacheCreatesFreshArchitecture()
+ {
+ // Two architectures built with WithoutArchitectureCache for the same assembly
+ // should be distinct object instances.
+ var arch1 = new ArchLoader()
+ .WithoutArchitectureCache()
+ .LoadAssembly(typeof(BaseClass).Assembly)
+ .Build();
+
+ var arch2 = new ArchLoader()
+ .WithoutArchitectureCache()
+ .LoadAssembly(typeof(BaseClass).Assembly)
+ .Build();
+
+ Assert.NotSame(arch1, arch2);
+ }
+
+ [Fact]
+ public void WithoutRuleEvaluationCacheAndWithoutArchitectureCacheCombined()
+ {
+ // Build two architectures with caching disabled + WithoutArchitectureCache
+ // from the same assembly. They should be distinct instances, each with no caching.
+ var arch1 = new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
+ .LoadAssembly(typeof(BaseClass).Assembly)
+ .Build();
+
+ var arch2 = new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
+ .LoadAssembly(typeof(BaseClass).Assembly)
+ .Build();
+
+ Assert.NotSame(arch1, arch2);
+
+ // Verify caching is disabled
+ var callCount = 0;
+ var provider = new TestObjectProvider("combined-test");
+
+ arch1.GetOrCreateObjects(
+ provider,
+ _ =>
+ {
+ callCount++;
+ return Enumerable.Empty();
+ }
+ );
+ arch1.GetOrCreateObjects(
+ provider,
+ _ =>
+ {
+ callCount++;
+ return Enumerable.Empty();
+ }
+ );
+
+ Assert.Equal(2, callCount);
+ }
+
+ ///
+ /// Minimal IObjectProvider implementation for ArchLoader integration tests.
+ ///
+ private class TestObjectProvider : IObjectProvider
+ {
+ public TestObjectProvider(string description)
+ {
+ Description = description;
+ }
+
+ public string Description { get; }
+
+ public IEnumerable GetObjects(Architecture architecture)
+ {
+ return Enumerable.Empty();
+ }
+
+ public string FormatDescription(
+ string emptyDescription,
+ string singleDescription,
+ string multipleDescription
+ )
+ {
+ return Description;
+ }
+
+ public override int GetHashCode()
+ {
+ return Description != null ? Description.GetHashCode() : 0;
+ }
+ }
+
[Fact]
public void LoadAssemblies()
{
diff --git a/ArchUnitNETTests/Loader/RegexUtilsTests.cs b/ArchUnitNETTests/Loader/RegexUtilsTests.cs
index 05d5636e4..0bb6c3bfe 100644
--- a/ArchUnitNETTests/Loader/RegexUtilsTests.cs
+++ b/ArchUnitNETTests/Loader/RegexUtilsTests.cs
@@ -1,151 +1,7 @@
-using System.Linq;
-using System.Text;
-using ArchUnitNET.Domain;
-using ArchUnitNET.Domain.Extensions;
-using ArchUnitNET.Loader;
using ArchUnitNETTests.Domain.Dependencies.Members;
-using Xunit;
namespace ArchUnitNETTests.Loader
{
- public class RegexUtilsTest
- {
- private static readonly Architecture Architecture =
- StaticTestArchitectures.ArchUnitNETTestArchitecture;
- private static readonly string _nonMatch = "Not expected to match.";
- private readonly PropertyMember _autoPropertyMember;
- private readonly string _expectedGetMethodFullName;
- private readonly string _expectedGetMethodName;
- private readonly string _expectedSetMethodName;
- private readonly string _nonMatchEmpty = string.Empty;
-
- public RegexUtilsTest()
- {
- var propertyClass = Architecture.GetClassOfType(typeof(BackingFieldExamples));
- _autoPropertyMember = propertyClass.GetPropertyMembersWithName("AutoProperty").Single();
- _expectedGetMethodName = BuildExpectedGetMethodName(_autoPropertyMember);
- _expectedGetMethodFullName = BuildExpectedGetMethodFullName(_autoPropertyMember);
- _expectedSetMethodName = BuildExpectedSetMethodName(
- _autoPropertyMember,
- _autoPropertyMember.DeclaringType
- );
- }
-
- private static string BuildExpectedGetMethodName(
- PropertyMember propertyMember,
- params IType[] parameterTypes
- )
- {
- var builder = new StringBuilder();
- builder.Append("get_");
- builder.Append(propertyMember.Name);
- builder = AddParameterTypesToMethodName(builder, parameterTypes);
- return builder.ToString();
- }
-
- private static string BuildExpectedSetMethodName(
- PropertyMember propertyMember,
- params IType[] parameterTypes
- )
- {
- var builder = new StringBuilder();
- builder.Append("set_");
- builder.Append(propertyMember.Name);
- builder = AddParameterTypesToMethodName(builder, parameterTypes);
- return builder.ToString();
- }
-
- private static string BuildExpectedGetMethodFullName(
- PropertyMember propertyMember,
- params IType[] parameterTypes
- )
- {
- var builder = new StringBuilder();
- builder.Append(propertyMember.DeclaringType.FullName);
- builder.Append(" ");
- builder.Append(propertyMember.DeclaringType.Name);
- builder.Append("::get_");
- builder.Append(propertyMember.Name);
- builder = AddParameterTypesToMethodName(builder, parameterTypes);
- return builder.ToString();
- }
-
- private static StringBuilder AddParameterTypesToMethodName(
- StringBuilder nameBuilder,
- params IType[] parameterTypeNames
- )
- {
- nameBuilder.Append("(");
- for (var index = 0; index < parameterTypeNames.Length; ++index)
- {
- if (index > 0)
- {
- nameBuilder.Append(",");
- }
-
- nameBuilder.Append(parameterTypeNames[index].FullName);
- }
-
- nameBuilder.Append(")");
- return nameBuilder;
- }
-
- [Fact]
- public void GetMethodFullNameRegexRecognizesNonMatch()
- {
- Assert.Null(RegexUtils.MatchGetPropertyName(_nonMatchEmpty));
- Assert.Null(RegexUtils.MatchGetPropertyName(_nonMatch));
- }
-
- [Fact]
- public void GetMethodNameRegexRecognizesNonMatch()
- {
- Assert.Null(RegexUtils.MatchGetPropertyName(_nonMatchEmpty));
- Assert.Null(RegexUtils.MatchGetPropertyName(_nonMatch));
- }
-
- [Fact]
- public void GetMethodPropertyMemberFullNameRegexMatchAsExpected()
- {
- Assert.Equal(
- _autoPropertyMember.Name,
- RegexUtils.MatchGetPropertyName(_expectedGetMethodFullName)
- );
- }
-
- [Fact]
- public void GetMethodPropertyMemberRegexMatchAsExpected()
- {
- Assert.Equal(
- _autoPropertyMember.Name,
- RegexUtils.MatchGetPropertyName(_expectedGetMethodName)
- );
- }
-
- [Fact]
- public void SetMethodFullNameRegexRecognizesNonMatch()
- {
- Assert.Null(RegexUtils.MatchSetPropertyName(_nonMatchEmpty));
- Assert.Null(RegexUtils.MatchSetPropertyName(_nonMatch));
- }
-
- [Fact]
- public void SetMethodNameRegexRecognizesNonMatch()
- {
- Assert.Null(RegexUtils.MatchSetPropertyName(_nonMatchEmpty));
- Assert.Null(RegexUtils.MatchSetPropertyName(_nonMatch));
- }
-
- [Fact]
- public void SetMethodPropertyMemberRegexMatchAsExpected()
- {
- Assert.Equal(
- _autoPropertyMember.Name,
- RegexUtils.MatchSetPropertyName(_expectedSetMethodName)
- );
- }
- }
-
public class BackingFieldExamples
{
private ChildField _fieldPropertyPair;
diff --git a/ArchUnitNETTests/StaticTestArchitectures.cs b/ArchUnitNETTests/StaticTestArchitectures.cs
index 28ea6143d..ca69fa9c7 100644
--- a/ArchUnitNETTests/StaticTestArchitectures.cs
+++ b/ArchUnitNETTests/StaticTestArchitectures.cs
@@ -20,18 +20,32 @@ public static class StaticTestArchitectures
.Build();
public static readonly Architecture AttributeArchitecture = new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
.LoadAssemblies(typeof(AttributeNamespace.ClassWithoutAttributes).Assembly)
.Build();
public static readonly Architecture DependencyArchitecture = new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
.LoadAssemblies(typeof(TypeDependencyNamespace.BaseClass).Assembly)
.Build();
+ public static readonly Architecture MethodDependencyArchitecture = new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
+ .LoadAssemblies(typeof(MethodDependencyNamespace.MethodDependencyClass).Assembly)
+ .Build();
+
public static readonly Architecture InterfaceArchitecture = new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
.LoadAssemblies(typeof(InterfaceAssembly.IBaseInterface).Assembly)
.Build();
public static readonly Architecture LoaderTestArchitecture = new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
.LoadAssemblies(
typeof(LoaderTestAssembly.LoaderTestAssembly).Assembly,
typeof(OtherLoaderTestAssembly.OtherLoaderTestAssembly).Assembly
@@ -39,6 +53,8 @@ public static class StaticTestArchitectures
.Build();
public static readonly Architecture VisibilityArchitecture = new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
.LoadAssemblies(typeof(VisibilityNamespace.PublicClass).Assembly)
.Build();
diff --git a/documentation/docs/guide.md b/documentation/docs/guide.md
index 0ed4d8dd4..8d75c21b3 100644
--- a/documentation/docs/guide.md
+++ b/documentation/docs/guide.md
@@ -40,6 +40,30 @@ private static readonly Architecture Architecture =
System.Reflection.Assembly.Load("ForbiddenClassAssemblyName")
).Build();
```
+#### 2.2.1. Caching
+
+ArchUnitNET uses two levels of caching to improve performance:
+
+**Architecture Cache** — When you call `Build()`, the resulting `Architecture` is stored in a global singleton cache keyed by the loaded assemblies. If you load the same set of assemblies again (e.g., in a different test class), the cached architecture is returned instead of re-analyzing everything.
+
+**Rule Evaluation Cache** — When architecture rules are evaluated, the filtered collections produced by object providers (e.g., `Types().That().ResideInNamespace(...)`) are cached within the architecture. If the same provider is used in multiple rules, the cached result is reused.
+
+Both caches are enabled by default. You can configure them via `ArchLoader`:
+
+```cs
+// Disable both rule evaluation and architecture caching
+private static readonly Architecture Architecture =
+ new ArchLoader()
+ .WithoutRuleEvaluationCache()
+ .WithoutArchitectureCache()
+ .LoadAssemblies(System.Reflection.Assembly.Load("ExampleClassAssemblyName"))
+ .Build();
+```
+
+`WithoutRuleEvaluationCache()` disables the rule evaluation cache so that each call to `GetOrCreateObjects` always executes the provider's filtering logic. This is useful in test scenarios where you need to ensure every provider independently exercises its code path.
+
+`WithoutArchitectureCache()` bypasses the global architecture cache, so each `Build()` call creates a fresh architecture instance. This is typically used together with `WithoutRuleEvaluationCache()` to avoid caching an architecture with non-standard caching settings in the global cache.
+
#### 2.3. Declare Layers
Declare variables you'll use throughout your tests up here
```cs