diff --git a/README.md b/README.md index b1136dc..a306b6c 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ ___ Name | MicroElements.Collections.Sources Description | MicroElements source only package: Collection extensions: NotNull, Iterate, Execute, WhereNotNull, Materialize, IncludeByWildcardPatterns, ExcludeByWildcardPatterns. - Special collections: TwoLayerCache. + Special collections: Cache, TwoLayerCache, PollingCache. Github | [https://github.com/micro-elements/MicroElements.Shared/tree/master/src/MicroElements.Collections.Sources](https://github.com/micro-elements/MicroElements.Shared/tree/master/src/MicroElements.Collections.Sources) Status | [![NuGetVersion](https://img.shields.io/nuget/v/MicroElements.Collections.Sources.svg)](https://www.nuget.org/packages/MicroElements.Collections.Sources) ![NuGetDownloads](https://img.shields.io/nuget/dt/MicroElements.Collections.Sources.svg) diff --git a/src/MicroElements.Collections.Sources/README.md b/src/MicroElements.Collections.Sources/README.md index c377e86..2452500 100644 --- a/src/MicroElements.Collections.Sources/README.md +++ b/src/MicroElements.Collections.Sources/README.md @@ -4,7 +4,7 @@ MicroElements source only package: Collection extensions: NotNull, Iterate, Execute, WhereNotNull, Materialize, IncludeByWildcardPatterns, ExcludeByWildcardPatterns. - Special collections: TwoLayerCache. + Special collections: Cache, TwoLayerCache, PollingCache. ## Extensions diff --git a/src/MicroElements.Reflection.Sources/CHANGELOG.md b/src/MicroElements.Reflection.Sources/CHANGELOG.md index 81c5c7a..8b06b1d 100644 --- a/src/MicroElements.Reflection.Sources/CHANGELOG.md +++ b/src/MicroElements.Reflection.Sources/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [1.9.0] - 2023-10-21 +- Changed: `ITypeCache` become `IEnumerable` +- Changed: `TypeLoader` unified assembly loading, `ExcludeByPatterns` fix +- Added: `TypeCache.Create` method to create type cache with customizations +- Added: `Invoker` creates compiled cached delegates more easy than `CodeCompiler` + ## [1.8.0] - 2022-12-17 - Changed: `MicroElements.Reflection.TypeCache` renamed to `MicroElements.Reflection.TypeCaching` - Added: `LazyTypeCache` diff --git a/src/MicroElements.Reflection.Sources/MicroElements.Reflection.Sources.csproj b/src/MicroElements.Reflection.Sources/MicroElements.Reflection.Sources.csproj index 1b03b98..6bb32cd 100644 --- a/src/MicroElements.Reflection.Sources/MicroElements.Reflection.Sources.csproj +++ b/src/MicroElements.Reflection.Sources/MicroElements.Reflection.Sources.csproj @@ -8,7 +8,7 @@ netstandard2.1 MicroElements.Reflection.Sources - 1.8.0 + 1.9.0 MicroElements source only package: Reflection. Classes: TypeExtensions, TypeCheck, ObjectExtensions, Expressions, CodeCompiler, FriendlyName. MicroElements Reflection Expressions diff --git a/src/MicroElements.Reflection.Sources/MicroElements/Reflection/CodeCompiler.cs b/src/MicroElements.Reflection.Sources/MicroElements/Reflection/CodeCompiler.cs index 731e601..bd0e444 100644 --- a/src/MicroElements.Reflection.Sources/MicroElements/Reflection/CodeCompiler.cs +++ b/src/MicroElements.Reflection.Sources/MicroElements/Reflection/CodeCompiler.cs @@ -11,8 +11,11 @@ namespace MicroElements.Reflection.CodeCompiler { using System; using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; using System.Linq.Expressions; using System.Reflection; + using MicroElements.Reflection.FriendlyName; /// /// Creates compiled functions. @@ -21,59 +24,21 @@ internal static partial class CodeCompiler { #region Caches - private readonly struct FuncKey : IEquatable - { - /// - /// Function type. - /// - private readonly Type _type; - - /// - /// Function name. - /// - private readonly string _name; - - /// - /// Initializes a new instance of the struct. - /// - /// Function type. - /// Function name. - public FuncKey(Type type, string name) - { - _type = type; - _name = name; - } - - /// - public bool Equals(FuncKey other) => _name == other._name && _type == other._type; - - /// - public override bool Equals(object? obj) => obj is FuncKey other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(_name, _type); - - public static bool operator ==(FuncKey left, FuncKey right) => left.Equals(right); - - public static bool operator !=(FuncKey left, FuncKey right) => !left.Equals(right); - } + private readonly record struct FuncKey(Type Type, string Name); private static class Cache { - public static readonly ConcurrentDictionary> FuncCache = - new ConcurrentDictionary>(); + public static readonly ConcurrentDictionary> FuncCache = new (); } private static class Cache { - public static readonly ConcurrentDictionary> FuncCache = - new ConcurrentDictionary>(); + public static readonly ConcurrentDictionary> FuncCache = new (); } private static class Cache { - public static readonly ConcurrentDictionary> FuncCache = - new ConcurrentDictionary>(); + public static readonly ConcurrentDictionary> FuncCache = new (); } #endregion @@ -97,7 +62,7 @@ public static Func CachedCompiledFunc(Type type, str { if (!Cache.FuncCache.TryGetValue(new FuncKey(type, name), out var cachedFunc)) { - cachedFunc = CompileGeneric(type, genericMethodFunc); + cachedFunc = CompileGeneric(genericMethodFunc, type); Cache.FuncCache.TryAdd(new FuncKey(type, name), cachedFunc); } @@ -119,7 +84,7 @@ public static Func CachedCompiledFunc(Ty { if (!Cache.FuncCache.TryGetValue(new FuncKey(type, name), out var cachedFunc)) { - cachedFunc = CompileGeneric(type, genericMethodFunc); + cachedFunc = CompileGeneric(genericMethodFunc, type); Cache.FuncCache.TryAdd(new FuncKey(type, name), cachedFunc); } @@ -142,62 +107,237 @@ public static Func CachedCompiledFunc.FuncCache.TryGetValue(new FuncKey(type, name), out var cachedFunc)) { - cachedFunc = CompileGeneric(type, genericMethodFunc); + cachedFunc = CompileGeneric(genericMethodFunc, type); Cache.FuncCache.TryAdd(new FuncKey(type, name), cachedFunc); } return cachedFunc; } - private static Func CompileGeneric(Type propertyType, Func genericMethodFunc) + private static Func CompileGeneric(Func genericMethodFunc, Type genericArg1) + { + MethodInfo genericMethod = genericMethodFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArg1); + return Invoker.CompileMethod(genericMethod); + } + + private static Func CompileGeneric(Func genericMethodFunc, Type genericArg1) + { + MethodInfo genericMethod = genericMethodFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArg1); + return Invoker.CompileMethod(genericMethod); + } + + private static Func CompileGeneric(Func genericMethodFunc, Type genericArg1) { - MethodInfo methodInfo = genericMethodFunc.Method.GetGenericMethodDefinition(); - return Compile(propertyType, methodInfo); + MethodInfo genericMethod = genericMethodFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArg1); + return Invoker.CompileMethod(genericMethod); } + } - private static Func CompileGeneric(Type propertyType, Func genericMethodFunc) + /// + /// Invoker creates compiled cached delegates. + /// + internal static class Invoker + { + #region Caches + + private static class FuncCache { - MethodInfo methodInfo = genericMethodFunc.Method.GetGenericMethodDefinition(); - return Compile(propertyType, methodInfo); + internal static ConcurrentDictionary<(Type MethodOwner, string MethodName, Type? GenericArg1, Type? GenericArg2), Func> Cache = new(); } - private static Func CompileGeneric(Type propertyType, Func genericMethodFunc) + private static class FuncCache { - MethodInfo methodInfo = genericMethodFunc.Method.GetGenericMethodDefinition(); - return Compile(propertyType, methodInfo); + internal static ConcurrentDictionary<(Type MethodOwner, string MethodName, Type? GenericArg1, Type? GenericArg2), Func> Cache = new(); } - private static Func Compile(Type propertyType, MethodInfo methodInfo) + private static class FuncCache { - MethodInfo genericMethod = methodInfo.MakeGenericMethod(propertyType); - ParameterExpression arg0 = Expression.Parameter(typeof(Arg0)); - MethodCallExpression callExpression = Expression.Call(genericMethod, arg0); + internal static ConcurrentDictionary<(Type MethodOwner, string MethodName, Type? GenericArg1, Type? GenericArg2), Func> Cache = new(); + } + + #endregion + + public static Func GetCompiledCachedMethod( + string methodName, Type? genericArg1 = null, Type? genericArg2 = null) + { + var cacheKey = (typeof(TInstance), MethodName: methodName, GenericArg1: genericArg1, GenericArg2: genericArg2); + return FuncCache.Cache.GetOrAdd(cacheKey, + a => CompileMethod(a.MethodOwner, a.MethodName, a.GenericArg1, a.GenericArg2)); + } + + public static Func GetCompiledCachedMethod( + string methodName, Type? genericArg1 = null, Type? genericArg2 = null) + { + var cacheKey = (typeof(TInstance), MethodName: methodName, GenericArg1: genericArg1, GenericArg2: genericArg2); + return FuncCache.Cache.GetOrAdd(cacheKey, + a => CompileMethod(a.MethodOwner, a.MethodName, a.GenericArg1, a.GenericArg2)); + } + + public static Func GetCompiledCachedMethod( + this Type methodOwner, string methodName, Type? genericArg1 = null, Type? genericArg2 = null) + { + var cacheKey = (MethodOwner: methodOwner, MethodName: methodName, GenericArg1: genericArg1, GenericArg2: genericArg2); + return FuncCache.Cache.GetOrAdd(cacheKey, + a => CompileMethod(a.MethodOwner, a.MethodName, a.GenericArg1, a.GenericArg2)); + } + + public static Func GetCompiledCachedMethod( + this Type methodOwner, string methodName, Type? genericArg1 = null, Type? genericArg2 = null) + { + var cacheKey = (MethodOwner: methodOwner, MethodName: methodName, GenericArg1: genericArg1, GenericArg2: genericArg2); + return FuncCache.Cache.GetOrAdd(cacheKey, + a => CompileMethod(a.MethodOwner, a.MethodName, a.GenericArg1, a.GenericArg2)); + } + + public static Func GetCompiledCachedMethod( + this Type methodOwner, string methodName, Type? genericArg1 = null, Type? genericArg2 = null) + { + var cacheKey = (MethodOwner: methodOwner, MethodName: methodName, GenericArg1: genericArg1, GenericArg2: genericArg2); + return FuncCache.Cache.GetOrAdd(cacheKey, + a => CompileMethod(a.MethodOwner, a.MethodName, a.GenericArg1, a.GenericArg2)); + } + + public static Func GetCompiledCachedMethod(Func genericMethodFunc, Type? genericArg1 = null, Type? genericArg2 = null) + { + var methodInfo = FromFunc(genericMethodFunc, genericArg1, genericArg2); + var cacheKey = (MethodOwner: methodInfo.DeclaringType, MethodName: methodInfo.Name, GenericArg1: genericArg1, GenericArg2: genericArg2); + return FuncCache.Cache.GetOrAdd(cacheKey, + a => CompileMethod(a.MethodOwner, a.MethodName, a.GenericArg1, a.GenericArg2)); + } + + public static Func GetCompiledCachedMethod(Func genericMethodFunc, Type? genericArg1 = null, Type? genericArg2 = null) + { + var methodInfo = FromFunc(genericMethodFunc, genericArg1, genericArg2); + var cacheKey = (MethodOwner: methodInfo.DeclaringType, MethodName: methodInfo.Name, GenericArg1: genericArg1, GenericArg2: genericArg2); + return FuncCache.Cache.GetOrAdd(cacheKey, + a => CompileMethod(a.MethodOwner, a.MethodName, a.GenericArg1, a.GenericArg2)); + } + + public static Func GetCompiledCachedMethod(Func genericMethodFunc, Type? genericArg1 = null, Type? genericArg2 = null) + { + var methodInfo = FromFunc(genericMethodFunc, genericArg1, genericArg2); + var cacheKey = (MethodOwner: methodInfo.DeclaringType, MethodName: methodInfo.Name, GenericArg1: genericArg1, GenericArg2: genericArg2); + return FuncCache.Cache.GetOrAdd(cacheKey, + a => CompileMethod(a.MethodOwner, a.MethodName, a.GenericArg1, a.GenericArg2)); + } + + public static MethodInfo GetMethod(this Type methodOwner, string methodName, Type? genericArg1, Type? genericArg2) + { + BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; + + var methodInfo = methodOwner.GetMethod(methodName, bindingFlags); + if (methodInfo is null) + throw new InvalidOperationException($"Type {methodOwner.GetFriendlyName()} has no method {methodName}"); + + return MakeGenericMethodIfGeneric(methodInfo, genericArg1, genericArg2); + } + + public static MethodInfo MakeGenericMethodIfGeneric(this MethodInfo methodInfo, Type? genericArg1, Type? genericArg2) + { + if (methodInfo.IsGenericMethodDefinition) + { + Type[] methodGenericArguments = GetArgs(genericArg1, genericArg2).ToArray(); + methodInfo = methodInfo.MakeGenericMethod(methodGenericArguments); + } + + return methodInfo; + + static IEnumerable GetArgs(Type? genericArg1, Type? genericArg2) + { + if (genericArg1 != null) yield return genericArg1; + if (genericArg2 != null) yield return genericArg2; + } + } + + public static MethodInfo EnsureIsGenericMethodDefinition(this MethodInfo methodInfo) + { + if (!methodInfo.IsGenericMethodDefinition) + throw new InvalidOperationException("Method should be generic. Use method group instead of lambda."); + return methodInfo; + } + + public static Func CompileMethod( + this Type methodOwner, string methodName, Type? genericArg1, Type? genericArg2) + { + var genericMethod = GetMethod(methodOwner, methodName, genericArg1, genericArg2); + return CompileMethod(genericMethod); + } + + public static Func CompileMethod( + this Type methodOwner, string methodName, Type? genericArg1, Type? genericArg2) + { + var genericMethod = GetMethod(methodOwner, methodName, genericArg1, genericArg2); + return CompileMethod(genericMethod); + } + + public static Func CompileMethod( + this Type methodOwner, string methodName, Type? genericArg1, Type? genericArg2) + { + var genericMethod = GetMethod(methodOwner, methodName, genericArg1, genericArg2); + return CompileMethod(genericMethod); + } + + public static Func CompileMethod(this MethodInfo method) + { + var arg1 = Expression.Parameter(typeof(TMethodArg1), "arg1"); + + MethodCallExpression callExpression = method.IsStatic ? + Expression.Call(null, method, arg1) : + Expression.Call(arg1, method); + return Expression - .Lambda>(callExpression, arg0) + .Lambda>(callExpression, arg1) .Compile(); } - private static Func Compile(Type propertyType, MethodInfo methodInfo) + public static Func CompileMethod(this MethodInfo method) { - MethodInfo genericMethod = methodInfo.MakeGenericMethod(propertyType); - ParameterExpression arg0 = Expression.Parameter(typeof(Arg0)); - ParameterExpression arg1 = Expression.Parameter(typeof(Arg1)); - MethodCallExpression callExpression = Expression.Call(genericMethod, arg0, arg1); + var arg1 = Expression.Parameter(typeof(TMethodArg1), "arg1"); + var arg2 = Expression.Parameter(typeof(TMethodArg2), "arg2"); + + MethodCallExpression callExpression = method.IsStatic ? + Expression.Call(null, method, arg1, arg2) : + Expression.Call(arg1, method, arg2); + return Expression - .Lambda>(callExpression, arg0, arg1) + .Lambda>(callExpression, arg1, arg2) .Compile(); } - private static Func Compile(Type propertyType, MethodInfo methodInfo) + public static Func CompileMethod(this MethodInfo method) { - MethodInfo genericMethod = methodInfo.MakeGenericMethod(propertyType); - ParameterExpression arg0 = Expression.Parameter(typeof(Arg0)); - ParameterExpression arg1 = Expression.Parameter(typeof(Arg1)); - ParameterExpression arg2 = Expression.Parameter(typeof(Arg2)); - MethodCallExpression callExpression = Expression.Call(genericMethod, arg0, arg1, arg2); + var arg1 = Expression.Parameter(typeof(TMethodArg1), "arg1"); + var arg2 = Expression.Parameter(typeof(TMethodArg2), "arg2"); + var arg3 = Expression.Parameter(typeof(TMethodArg3), "arg3"); + + MethodCallExpression callExpression = method.IsStatic ? + Expression.Call(null, method, arg1, arg2, arg3) : + Expression.Call(arg1, method, arg2, arg3); + return Expression - .Lambda>(callExpression, arg0, arg1, arg2) + .Lambda>(callExpression, arg1, arg2, arg3) .Compile(); } + + public static MethodInfo FromFunc(Func genericMethodGroup, Type? genericArg1 = null, Type? genericArg2 = null) + { + return genericMethodGroup.Method + .GetGenericMethodDefinition() + .MakeGenericMethodIfGeneric(genericArg1, genericArg2); + } + + public static MethodInfo FromFunc(Func genericMethodGroup, Type? genericArg1 = null, Type? genericArg2 = null) + { + return genericMethodGroup.Method + .GetGenericMethodDefinition() + .MakeGenericMethodIfGeneric(genericArg1, genericArg2); + } + + public static MethodInfo FromFunc(Func genericMethodGroup, Type? genericArg1 = null, Type? genericArg2 = null) + { + return genericMethodGroup.Method + .GetGenericMethodDefinition() + .MakeGenericMethodIfGeneric(genericArg1, genericArg2); + } } } diff --git a/src/MicroElements.Reflection.Sources/MicroElements/Reflection/TypeCache.cs b/src/MicroElements.Reflection.Sources/MicroElements/Reflection/TypeCache.cs index b3d3a5b..288d9dc 100644 --- a/src/MicroElements.Reflection.Sources/MicroElements/Reflection/TypeCache.cs +++ b/src/MicroElements.Reflection.Sources/MicroElements/Reflection/TypeCache.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. #endregion #region Supressions + #pragma warning disable // ReSharper disable All #endregion @@ -10,6 +11,7 @@ namespace MicroElements.Reflection.TypeCaching { using System; + using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -18,7 +20,7 @@ namespace MicroElements.Reflection.TypeCaching using MicroElements.Collections.Extensions.NotNull; /// Represents type cache abstraction. - internal interface ITypeCache + internal interface ITypeCache: IEnumerable { Type? GetType(string typeName); string? GetName(Type type); @@ -125,6 +127,12 @@ public void AddType(Type type, string typeName) aliasForType[type] = typeName; } } + + /// + public IEnumerator GetEnumerator() => _types.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } /// Lazy cache. Gets values only on first attempt. @@ -149,6 +157,12 @@ public LazyTypeCache(Func factory) /// public void AddType(Type type, string typeName) => _typeCache.Value.AddType(type, typeName); + + /// + public IEnumerator GetEnumerator() => _typeCache.Value.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } /// Type cache that gets value from parent if it was not found in current cache. @@ -168,6 +182,23 @@ public HierarchicalTypeCache(ITypeCache parent, ITypeCache typeCache) public string? GetName(Type type) => _typeCache.GetName(type) ?? _parent.GetName(type); public void AddType(Type type, string typeName) => _typeCache.AddType(type, typeName); + + /// + public IEnumerator GetEnumerator() + { + foreach (var type in _typeCache) + { + yield return type; + } + + foreach (var type in _parent) + { + yield return type; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } internal partial class TypeCache @@ -185,6 +216,12 @@ public static ITypeCache CreateAppDomainCache(bool reloadOnAssemblyLoad = false) AppDomain.CurrentDomain.AssemblyLoad += (sender, args) => typeCache.Invalidate(); return typeCache; } + + /// Creates type cache from AssemblySource and TypeFilter. + public static ITypeCache Create(AssemblySource assemblySource, TypeFilters typeFilter) + { + return new LazyTypeCache(() => new TypeCache(assemblySource.LoadTypes(typeFilter))); + } } internal static class TypeCacheExtensions diff --git a/src/MicroElements.Reflection.Sources/MicroElements/Reflection/TypeLoader.cs b/src/MicroElements.Reflection.Sources/MicroElements/Reflection/TypeLoader.cs index 4c0fec6..390574f 100644 --- a/src/MicroElements.Reflection.Sources/MicroElements/Reflection/TypeLoader.cs +++ b/src/MicroElements.Reflection.Sources/MicroElements/Reflection/TypeLoader.cs @@ -40,7 +40,7 @@ public static IReadOnlyCollection LoadTypes( .GetTypes(typeFilters, messages); return types; } - + /// /// Loads assemblies according . /// 1. Gets all assemblies from if is true. @@ -56,42 +56,33 @@ public static IEnumerable LoadAssemblies( { assemblySource.AssertArgumentNotNull(nameof(assemblySource)); - IEnumerable assemblies = Array.Empty(); - - if (assemblySource.LoadFromDomain) - assemblies = AppDomain.CurrentDomain.GetAssemblies(); - - if (assemblySource.LoadFromDirectory != null) + IEnumerable assemblies = Array.Empty() + .ConcatIf(assemblySource.LoadFromDomain, AppDomain.CurrentDomain.GetAssemblies) + .ConcatIf(assemblySource.LoadFromDirectory != null, AssembliesFromDirectory) + .ConcatIf(assemblySource.Assemblies != null, () => assemblySource.Assemblies) + .IncludeByPatterns(assembly => assembly.FullName, assemblySource.IncludePatterns) + .ExcludeByPatterns(assembly => assembly.FullName, assemblySource.ExcludePatterns) + .Distinct(); + + return assemblies; + + IEnumerable AssembliesFromDirectory() { if (!Directory.Exists(assemblySource.LoadFromDirectory)) throw new DirectoryNotFoundException($"Assembly ScanDirectory {assemblySource.LoadFromDirectory} is not exists."); - var searchPatterns = assemblySource.SearchPatterns ?? new[] { "*.dll" }; + var searchPatterns = assemblySource.FileSearchPatterns ?? new[] { "*.dll" }; var assembliesFromDirectory = searchPatterns - .SelectMany(filePattern => Directory.EnumerateFiles(assemblySource.LoadFromDirectory, filePattern, SearchOption.TopDirectoryOnly)) + .SelectMany(filePattern => Directory.EnumerateFiles(assemblySource.LoadFromDirectory!, filePattern, SearchOption.TopDirectoryOnly)) .IncludeByPatterns(fileName => fileName, assemblySource.IncludePatterns) .ExcludeByPatterns(fileName => fileName, assemblySource.ExcludePatterns) .Select(assemblyFile => TryLoadAssemblyFrom(assemblyFile, messages)!) .Where(assembly => assembly != null); - - assemblies = assemblies.Concat(assembliesFromDirectory); + return assembliesFromDirectory; } - - if (assemblySource.Assemblies is { Count: > 0 }) - { - assemblies = assemblies.Concat(assemblySource.Assemblies); - } - - assemblies = assemblies - .IncludeByPatterns(assembly => assembly.FullName, assemblySource.IncludePatterns) - .ExcludeByPatterns(assembly => assembly.FullName, assemblySource.ExcludePatterns); - - assemblies = assemblies.Distinct(); - - return assemblies; } - + /// /// Gets types from assembly list according type filters. /// @@ -170,27 +161,12 @@ public static IEnumerable GetDefinedTypesSafe(this Assembly assembly, ICol } } } - + /// /// Assembly source. /// - internal class AssemblySource + internal partial class AssemblySource { - /// - /// Gets an empty assembly source. No assemblies, no filters. - /// - public static AssemblySource Empty { get; } = new ( - loadFromDomain: false, - loadFromDirectory: null); - - /// - /// All assemblies from AppDomain. - /// - public static AssemblySource AppDomain { get; } = new ( - loadFromDomain: true, - loadFromDirectory: null, - filterByTypeFilters: true); - /// Load assemblies from . public bool LoadFromDomain { get; set; } @@ -200,7 +176,7 @@ internal class AssemblySource /// /// Optional file patterns for loading from directory. /// - public IReadOnlyCollection? SearchPatterns { get; set; } + public IReadOnlyCollection? FileSearchPatterns { get; set; } /// /// wildcard include patterns. @@ -219,37 +195,44 @@ internal class AssemblySource /// public IReadOnlyCollection? Assemblies { get; set; } - /// - /// Filter assemblies after type filtering and take only assemblies that owns filtered types. - /// - public bool FilterByTypeFilters { get; set; } = true; - /// /// Initializes a new instance of the class. /// /// Optional load assemblies from . /// Optional load assemblies from provided directory. - /// Optional file patterns for loading from directory. - /// Optional assembly filters. + /// Optional file patterns for loading from directory. /// User provided assemblies. - /// Filter assemblies after type filtering and take only assemblies that owns filtered types. public AssemblySource( bool loadFromDomain = false, string? loadFromDirectory = null, - IReadOnlyCollection? searchPatterns = null, - IReadOnlyCollection? assemblies = null, - bool filterByTypeFilters = true) + IReadOnlyCollection? fileSearchPatterns = null, + IReadOnlyCollection? assemblies = null) { LoadFromDomain = loadFromDomain; - + LoadFromDirectory = loadFromDirectory; - SearchPatterns = searchPatterns; - + FileSearchPatterns = fileSearchPatterns; + Assemblies = assemblies; - FilterByTypeFilters = filterByTypeFilters; } } + internal partial class AssemblySource + { + /// Gets an empty assembly source. No assemblies, no filters. + public static AssemblySource Empty { get; } = new (loadFromDomain: false); + + /// All assemblies from AppDomain. + public static AssemblySource AppDomain { get; } = new (loadFromDomain: true); + + /// All assemblies from AppDomain excluding system assemblies. + public static AssemblySource AppDomainExcludingSystem { get; } = new () + { + LoadFromDomain = true, + ExcludePatterns = new []{ "System.*", "Microsoft.*" } + }; + } + /// /// Type filters. /// @@ -263,7 +246,7 @@ internal class TypeFilters IsPublic = true, FullNameExcludes = new[] { "<*" } }; - + /// Include only public types. public bool IsPublic { get; set; } @@ -273,7 +256,7 @@ internal class TypeFilters /// Exclude types that matches filters. public IReadOnlyCollection? FullNameExcludes { get; set; } } - + /// /// Provides methods for filtering. /// @@ -283,6 +266,13 @@ internal static class Filtering internal static bool FileNameMatchesPattern(string filename, string pattern) => Regex.IsMatch(Path.GetFileName(filename) ?? string.Empty, WildcardToRegex(pattern)); + internal static IEnumerable ConcatIf(this IEnumerable values, bool predicate, Func?> valuesFactory) + { + if (predicate && valuesFactory() is {} valuesToAdd) + return values.Concat(valuesToAdd); + return values; + } + internal static IEnumerable IncludeByPatterns(this IEnumerable values, Func filterComponent, IReadOnlyCollection? includePatterns = null) { if (includePatterns == null) @@ -294,7 +284,7 @@ internal static IEnumerable ExcludeByPatterns(this IEnumerable values, { if (excludePatterns == null) return values; - return values.Where(value => excludePatterns.Any(excludePattern => !FileNameMatchesPattern(filterComponent(value), excludePattern))); + return values.Where(value => excludePatterns.All(excludePattern => !FileNameMatchesPattern(filterComponent(value), excludePattern))); } } } diff --git a/tests/MicroElements.Shared.Tests/MicroElements.Shared.Tests.csproj b/tests/MicroElements.Shared.Tests/MicroElements.Shared.Tests.csproj index 4e2ddce..1d87081 100644 --- a/tests/MicroElements.Shared.Tests/MicroElements.Shared.Tests.csproj +++ b/tests/MicroElements.Shared.Tests/MicroElements.Shared.Tests.csproj @@ -22,8 +22,8 @@ - - + + diff --git a/tests/MicroElements.Shared.Tests/Reflection/InvokerTests.cs b/tests/MicroElements.Shared.Tests/Reflection/InvokerTests.cs new file mode 100644 index 0000000..8e2ea5c --- /dev/null +++ b/tests/MicroElements.Shared.Tests/Reflection/InvokerTests.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using FluentAssertions; +using MicroElements.Reflection.CodeCompiler; +using Xunit; + +namespace MicroElements.Shared.Tests.Reflection; + +public class InvokerTests +{ + public record GenericArg1(string Value); + public record GenericArg2(string Value); + + public record Arg1(string Value); + public record Arg2(string Value); + + public record Result(string Value, Type? GenericType1 = null, Type? GenericType2 = null); + + public class SomeClass + { + public Result InstancePublic(Arg1 arg1) + { + return new Result(arg1.Value, null); + } + + public Result InstancePublicGeneric(Arg1 arg1) + { + return new Result(arg1.Value, typeof(TGenericArg1)); + } + + public static Result StaticGeneric1(Arg1 arg1) + { + return new Result(arg1.Value, typeof(TGenericArg1)); + } + + public static Result StaticGeneric2(Arg1 arg1, Arg2 arg2) + { + return new Result($"{arg1.Value}_{arg2.Value}", typeof(TGenericArg1)); + } + } + [Fact] + public void InstancePublic() + { + var func = Invoker.GetCompiledCachedMethod(nameof(SomeClass.InstancePublic)); + var instance = new SomeClass(); + var result = func(instance, new Arg1("arg1")); + result.Value.Should().Be("arg1"); + result.GenericType1.Should().Be(null); + } + + [Fact] + public void InstancePublicGeneric() + { + var func = Invoker.GetCompiledCachedMethod(nameof(SomeClass.InstancePublicGeneric), typeof(GenericArg1)); + var instance = new SomeClass(); + var result = func(instance, new Arg1("arg1")); + result.Value.Should().Be("arg1"); + result.GenericType1.Should().Be(typeof(GenericArg1)); + } + + [Fact] + public void StaticGeneric() + { + var compileGeneric = Invoker.GetCompiledCachedMethod(SomeClass.StaticGeneric1, typeof(GenericArg1)); + var result = compileGeneric(new Arg1("arg1")); + result.Value.Should().Be("arg1"); + result.GenericType1.Should().Be(typeof(GenericArg1)); + } + + [Fact] + public void StaticGeneric2() + { + var compileGeneric = Invoker.GetCompiledCachedMethod(SomeClass.StaticGeneric2, typeof(GenericArg1)); + var result = compileGeneric(new Arg1("arg1"), new Arg2("arg2")); + result.Value.Should().Be("arg1_arg2"); + result.GenericType1.Should().Be(typeof(GenericArg1)); + result.GenericType2.Should().Be(null); + } + + [Fact] + public void CompileGenericLocal() + { + var compileGeneric = Invoker.GetCompiledCachedMethod(LocalStaticGeneric, typeof(GenericArg1)); + var result = compileGeneric(new Arg1("arg1")); + result.Value.Should().Be("arg1"); + result.GenericType1.Should().Be(typeof(GenericArg1)); + + static Result LocalStaticGeneric(Arg1 arg1) + { + return new Result(arg1.Value, typeof(TGenericArg1)); + } + } +} \ No newline at end of file diff --git a/tests/MicroElements.Shared.Tests/Reflection/ReflectionTests.cs b/tests/MicroElements.Shared.Tests/Reflection/ReflectionTests.cs index 966fcb3..5862ea5 100644 --- a/tests/MicroElements.Shared.Tests/Reflection/ReflectionTests.cs +++ b/tests/MicroElements.Shared.Tests/Reflection/ReflectionTests.cs @@ -1,6 +1,9 @@ using System; +using System.Linq; using FluentAssertions; +using MicroElements.Reflection; using MicroElements.Reflection.ObjectExtensions; +using MicroElements.Reflection.TypeCaching; using MicroElements.Reflection.TypeExtensions; using Xunit; @@ -61,7 +64,10 @@ public void test_null_values() [Fact] public void test_type_cache() { - //TypeCache.NumericTypesWithNullable.Types.Count.Should().Be(22); + var array = TypeCache.AppDomainTypes.ToArray(); + var typeCache = TypeCache.Create(AssemblySource.AppDomainExcludingSystem, TypeFilters.AllPublicTypes).ToArray(); + + //TypeCache.CreateAppDomainCache() //TypeCache.NumericTypes.Types.Count.Should().Be(11); //// Use LocalDate to force load NodaTime assembly