From 7f2ba901391a8ec217f7d992832d008a817f4f94 Mon Sep 17 00:00:00 2001 From: "g.verdier" Date: Tue, 29 Jul 2025 17:55:13 +0200 Subject: [PATCH 1/2] Lazily load dynamic linq types --- .../DefaultDynamicLinqCustomTypeProvider.cs | 15 +++++----- .../Parser/KeywordsHelper.cs | 30 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs index 0b08d877..3200c494 100644 --- a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs +++ b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs @@ -2,12 +2,13 @@ using System.Linq.Dynamic.Core.Validation; using System.Reflection; using System.Runtime.CompilerServices; +using System.Threading; namespace System.Linq.Dynamic.Core.CustomTypeProviders; /// /// The default implementation for . -/// +/// /// Scans the current AppDomain for all types marked with , and adds them as custom Dynamic Link types. /// /// This class is used as default for full .NET Framework and .NET Core App 2.x and higher. @@ -16,8 +17,8 @@ public class DefaultDynamicLinqCustomTypeProvider : AbstractDynamicLinqCustomTyp { private readonly IAssemblyHelper _assemblyHelper; private readonly bool _cacheCustomTypes; + private readonly Lazy>? _cachedCustomTypes; - private HashSet? _cachedCustomTypes; private Dictionary>? _cachedExtensionMethods; /// @@ -49,6 +50,9 @@ public DefaultDynamicLinqCustomTypeProvider(ParsingConfig config, IList ad { _assemblyHelper = new DefaultAssemblyHelper(Check.NotNull(config)); _cacheCustomTypes = cacheCustomTypes; + _cachedCustomTypes = cacheCustomTypes + ? new Lazy>(GetCustomTypesInternal, LazyThreadSafetyMode.ExecutionAndPublication) + : null; } /// @@ -56,12 +60,7 @@ public virtual HashSet GetCustomTypes() { if (_cacheCustomTypes) { - if (_cachedCustomTypes == null) - { - _cachedCustomTypes = GetCustomTypesInternal(); - } - - return _cachedCustomTypes; + return _cachedCustomTypes!.Value; } return GetCustomTypesInternal(); diff --git a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs index 1e816384..1f5886eb 100644 --- a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; +using System.Threading; using AnyOfTypes; namespace System.Linq.Dynamic.Core.Parser; @@ -32,7 +33,7 @@ internal class KeywordsHelper : IKeywordsHelper private static readonly Dictionary PreDefinedTypeMapping = new(); // Custom DefinedTypes are not IgnoreCase - private readonly Dictionary _customTypeMapping = new(); + private readonly Lazy> _customTypeMapping; static KeywordsHelper() { @@ -66,22 +67,27 @@ public KeywordsHelper(ParsingConfig config) { FUNCTION_CAST, FUNCTION_CAST } }; + _customTypeMapping = new Lazy>(() => + { + Dictionary customTypeMapping = new(); + if (config.CustomTypeProvider != null) + { + foreach (var type in config.CustomTypeProvider.GetCustomTypes()) + { + customTypeMapping[type.FullName!] = type; + customTypeMapping[type.Name] = type; + } + } + + return customTypeMapping; + }, LazyThreadSafetyMode.PublicationOnly); + if (config.AreContextKeywordsEnabled) { _mappings.Add(KEYWORD_IT, KEYWORD_IT); _mappings.Add(KEYWORD_PARENT, KEYWORD_PARENT); _mappings.Add(KEYWORD_ROOT, KEYWORD_ROOT); } - - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (config.CustomTypeProvider != null) - { - foreach (var type in config.CustomTypeProvider.GetCustomTypes()) - { - _customTypeMapping[type.FullName!] = type; - _customTypeMapping[type.Name] = type; - } - } } public bool IsItOrRootOrParent(AnyOf value) @@ -125,7 +131,7 @@ public bool TryGetValue(string text, out AnyOf value) } // 5. Try to get as custom type - if (_customTypeMapping.TryGetValue(text, out var customType)) + if (_customTypeMapping.Value.TryGetValue(text, out var customType)) { value = customType; return true; From babd9f525724b0727f627b505c98c73f89ba71b4 Mon Sep 17 00:00:00 2001 From: "g.verdier" Date: Tue, 29 Jul 2025 22:29:54 +0200 Subject: [PATCH 2/2] Add lazy implementation --- .../Compatibility/Lazy.cs | 40 +++++++++++++++++++ .../Compatibility/LazyThreadSafetyMode.cs | 11 +++++ 2 files changed, 51 insertions(+) create mode 100644 src/System.Linq.Dynamic.Core/Compatibility/Lazy.cs create mode 100644 src/System.Linq.Dynamic.Core/Compatibility/LazyThreadSafetyMode.cs diff --git a/src/System.Linq.Dynamic.Core/Compatibility/Lazy.cs b/src/System.Linq.Dynamic.Core/Compatibility/Lazy.cs new file mode 100644 index 00000000..6085bf06 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Compatibility/Lazy.cs @@ -0,0 +1,40 @@ +using System.Threading; + +#if NET35 +namespace System +{ + internal class Lazy + { + private readonly Func _valueFactory; + private readonly object _lock; + + private T? _value; + private bool _valueCreated; + + public Lazy(Func valueFactory, LazyThreadSafetyMode mode) + { + _valueFactory = valueFactory; + _lock = new object(); + } + + public T Value + { + get + { + lock (_lock) + { + if (_valueCreated) + { + return _value!; + } + + _value = _valueFactory(); + _valueCreated = true; + return _value; + } + } + } + + } +} +#endif \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Compatibility/LazyThreadSafetyMode.cs b/src/System.Linq.Dynamic.Core/Compatibility/LazyThreadSafetyMode.cs new file mode 100644 index 00000000..f90be6f4 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Compatibility/LazyThreadSafetyMode.cs @@ -0,0 +1,11 @@ +#if NET35 +namespace System.Threading +{ + internal enum LazyThreadSafetyMode + { + None, + PublicationOnly, + ExecutionAndPublication, + } +} +#endif \ No newline at end of file