From 21d30cbd860f88785b93d95009bbe82a1810edfc Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 27 Jun 2026 09:47:37 +0200 Subject: [PATCH 1/2] By default, also support WellKnownTypes like StringComparer --- ...umerationsAndWellKnownTypesFromMscorlib.cs | 104 ++++++++++++++++++ .../Parser/EnumerationsFromMscorlib.cs | 59 ---------- .../Parser/ExpressionParser.cs | 11 +- .../Parser/KeywordsHelper.cs | 2 +- .../ExpressionTests.cs | 32 ++++++ 5 files changed, 147 insertions(+), 61 deletions(-) create mode 100644 src/System.Linq.Dynamic.Core/Parser/EnumerationsAndWellKnownTypesFromMscorlib.cs delete mode 100644 src/System.Linq.Dynamic.Core/Parser/EnumerationsFromMscorlib.cs diff --git a/src/System.Linq.Dynamic.Core/Parser/EnumerationsAndWellKnownTypesFromMscorlib.cs b/src/System.Linq.Dynamic.Core/Parser/EnumerationsAndWellKnownTypesFromMscorlib.cs new file mode 100644 index 00000000..9cb0e6f4 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/EnumerationsAndWellKnownTypesFromMscorlib.cs @@ -0,0 +1,104 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; + +namespace System.Linq.Dynamic.Core.Parser; + +internal static class EnumerationsAndWellKnownTypesFromMscorlib +{ + private readonly static string SystemPrivateCoreLib = typeof(StringComparer).GetTypeInfo().Assembly.FullName!; + private readonly static string SystemPrivateUri = typeof(UriFormat).GetTypeInfo().Assembly.FullName!; + private readonly static string SystemPrivateXml = typeof(XmlNodeType).GetTypeInfo().Assembly.FullName!; + private readonly static string SystemPrivateXmlLinq = typeof(XObject).GetTypeInfo().Assembly.FullName!; + + /// + /// Enum types and well-known types. + /// + public static readonly ConcurrentDictionary PredefinedEnumerationTypes = new(StringComparer.OrdinalIgnoreCase); + + static EnumerationsAndWellKnownTypesFromMscorlib() + { + var list = AddEnumsAndWellKnownTypesFromAssembly(SystemPrivateUri); + list.AddRange(AddEnumsAndWellKnownTypesFromAssembly(SystemPrivateCoreLib)); + list.AddRange(AddEnumsAndWellKnownTypesFromAssembly(SystemPrivateXml)); + list.AddRange(AddEnumsAndWellKnownTypesFromAssembly(SystemPrivateXmlLinq)); + +#if !(NET35 || NETSTANDARD1_3) + var systemPrivateDataContractSerialization = typeof(Runtime.Serialization.DataContractResolver).GetTypeInfo().Assembly.FullName!; + list.AddRange(AddEnumsAndWellKnownTypesFromAssembly(systemPrivateDataContractSerialization)); +#endif + foreach (var group in list.GroupBy(t => t.Name)) + { + Add(group); + } + } + + private static List AddEnumsAndWellKnownTypesFromAssembly(string assemblyName) + { + try + { + var assembly = Assembly.Load(new AssemblyName(assemblyName)); + var types = assembly.GetTypes().ToArray(); + + var enumTypes = types.Where(t => t.GetTypeInfo().IsEnum && t.GetTypeInfo().IsPublic); + var enumLikeTypes = FindEnumLikeTypes(types.Where(x => x == typeof(StringComparer)).ToArray()); + + return enumTypes.Union(enumLikeTypes).ToList(); + } + catch + { + return []; + } + } + + private static Type[] FindEnumLikeTypes(Type[] types) + { + try + { + return types + .Where(t => t.GetTypeInfo().IsPublic && !t.GetTypeInfo().IsEnum && HasStaticInstancesOfOwnType(t)) + .ToArray(); + } + catch + { + return []; + } + } + + private static bool HasStaticInstancesOfOwnType(Type type) + { + // Check for static properties that return the same type + var anyStaticProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Static) + .Any(p => p.PropertyType == type || p.PropertyType == type); + + if (anyStaticProperties) + { + return true; + } + + // Check for static fields that return the same type + var anyStaticFields = type.GetFields(BindingFlags.Public | BindingFlags.Static) + .Any(f => f.FieldType == type || f.FieldType == type); + + return anyStaticFields; + } + + private static void Add(IGrouping group) + { + if (group.Count() == 1) + { + var singleType = group.Single(); + PredefinedEnumerationTypes.TryAdd(group.Key, singleType); + PredefinedEnumerationTypes.TryAdd(singleType.FullName!, singleType); + } + else + { + foreach (var fullType in group) + { + PredefinedEnumerationTypes.TryAdd(fullType.FullName!, fullType); + } + } + } +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/EnumerationsFromMscorlib.cs b/src/System.Linq.Dynamic.Core/Parser/EnumerationsFromMscorlib.cs deleted file mode 100644 index 6b2f4b25..00000000 --- a/src/System.Linq.Dynamic.Core/Parser/EnumerationsFromMscorlib.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Reflection; - -namespace System.Linq.Dynamic.Core.Parser -{ - internal static class EnumerationsFromMscorlib - { - /// - /// All Enum types from mscorlib/netstandard. - /// - public static readonly IDictionary PredefinedEnumerationTypes = new ConcurrentDictionary(); - - static EnumerationsFromMscorlib() - { - var list = new List(AddEnumsFromAssembly(typeof(UriFormat).GetTypeInfo().Assembly.FullName!)); - -#if !(UAP10_0 || NETSTANDARD || NET35 || NETCOREAPP) - list.AddRange(AddEnumsFromAssembly("mscorlib")); -#else - list.AddRange(AddEnumsFromAssembly("System.Runtime")); - list.AddRange(AddEnumsFromAssembly("System.Private.Corelib")); -#endif - foreach (var group in list.GroupBy(t => t.Name)) - { - Add(group); - } - } - - private static IEnumerable AddEnumsFromAssembly(string assemblyName) - { - try - { - return Assembly.Load(new AssemblyName(assemblyName)).GetTypes().Where(t => t.GetTypeInfo().IsEnum && t.GetTypeInfo().IsPublic); - } - catch - { - return Enumerable.Empty(); - } - } - - private static void Add(IGrouping group) - { - if (group.Count() == 1) - { - var singleType = group.Single(); - PredefinedEnumerationTypes.Add(group.Key, singleType); - PredefinedEnumerationTypes.Add(singleType.FullName!, singleType); - } - else - { - foreach (var fullType in group) - { - PredefinedEnumerationTypes.Add(fullType.FullName!, fullType); - } - } - } - } -} diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 4a6319aa..d93cfde4 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -2317,7 +2317,16 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string { if (new[] { "Concat", "Contains", "ContainsKey", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union", "SequenceEqual" }.Contains(methodName)) { - args = [instance, args[0]]; + if (args.Length == 1) + { + args = [instance, args[0]]; + } + else + { + var argsAsList = new List { instance }; + argsAsList.AddRange(args); + args = argsAsList.ToArray(); + } } else { diff --git a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs index 1e816384..6adffdf5 100644 --- a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs @@ -118,7 +118,7 @@ public bool TryGetValue(string text, out AnyOf value) } // 4. Try to get as an enum from the system namespace - if (_config.SupportEnumerationsFromSystemNamespace && EnumerationsFromMscorlib.PredefinedEnumerationTypes.TryGetValue(text, out var predefinedEnumType)) + if (_config.SupportEnumerationsFromSystemNamespace && EnumerationsAndWellKnownTypesFromMscorlib.PredefinedEnumerationTypes.TryGetValue(text, out var predefinedEnumType)) { value = predefinedEnumType; return true; diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index 021183a5..d3d2a1e3 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -5,6 +5,8 @@ using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Tests.Helpers; using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using System.Net.WebSockets; +using System.Xml; using FluentAssertions; using Moq; using Newtonsoft.Json.Linq; @@ -853,6 +855,36 @@ public void ExpressionTests_Enum() Check.That(resultEqualStringMixedCaseParamRight.Single()).Equals(TestEnum.Var5); } + [Fact] + public void ExpressionTests_Enum_XmlNodeType() + { + // Arrange + var lst = new[] { XmlNodeType.Text, XmlNodeType.Element }; + var qry = lst.AsQueryable(); + + // Act + var result = lst.Count(it => new[] { XmlNodeType.Text }.Contains(it)); + var dynamicResult = qry.Count("new XmlNodeType[] { XmlNodeType.Text }.Contains(it)"); + + // Assert + dynamicResult.Should().Be(result); + } + + [Fact] + public void ExpressionTests_WellKnownTypes_StringComparer() + { + // Arrange + var lst = new[] { "test" }; + var qry = lst.AsQueryable(); + + // Act + var result = lst.Count(it => new string[] { "Test" }.Contains(it, StringComparer.OrdinalIgnoreCase)); + var dynamicResult = qry.Count("new string[] { \"Test\" }.Contains(it, StringComparer.OrdinalIgnoreCase)"); + + // Assert + dynamicResult.Should().Be(result); + } + [Fact] public void ExpressionTests_Enum_Property_Equality_Using_Argument() { From 5875ac460e754fff032bd39a77494ef9a695d78c Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 28 Jun 2026 22:51:56 +0200 Subject: [PATCH 2/2] . --- .../EnumerationsAndWellKnownTypesFromMscorlib.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/EnumerationsAndWellKnownTypesFromMscorlib.cs b/src/System.Linq.Dynamic.Core/Parser/EnumerationsAndWellKnownTypesFromMscorlib.cs index 9cb0e6f4..78f64cb6 100644 --- a/src/System.Linq.Dynamic.Core/Parser/EnumerationsAndWellKnownTypesFromMscorlib.cs +++ b/src/System.Linq.Dynamic.Core/Parser/EnumerationsAndWellKnownTypesFromMscorlib.cs @@ -58,7 +58,7 @@ private static Type[] FindEnumLikeTypes(Type[] types) try { return types - .Where(t => t.GetTypeInfo().IsPublic && !t.GetTypeInfo().IsEnum && HasStaticInstancesOfOwnType(t)) + .Where(t => t.GetTypeInfo().IsPublic && !t.GetTypeInfo().IsEnum && HasStaticPropertiesOrFieldsOfOwnType(t)) .ToArray(); } catch @@ -67,20 +67,20 @@ private static Type[] FindEnumLikeTypes(Type[] types) } } - private static bool HasStaticInstancesOfOwnType(Type type) + private static bool HasStaticPropertiesOrFieldsOfOwnType(Type type) { - // Check for static properties that return the same type + var baseType = type.GetTypeInfo().BaseType; + var anyStaticProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Static) - .Any(p => p.PropertyType == type || p.PropertyType == type); + .Any(p => p.PropertyType == type || p.PropertyType == baseType); if (anyStaticProperties) { return true; } - // Check for static fields that return the same type var anyStaticFields = type.GetFields(BindingFlags.Public | BindingFlags.Static) - .Any(f => f.FieldType == type || f.FieldType == type); + .Any(f => f.FieldType == type || f.FieldType == baseType); return anyStaticFields; }