diff --git a/src-blazor/WasmDynamicLinq/Pages/Home.razor.cs b/src-blazor/WasmDynamicLinq/Pages/Home.razor.cs index 667a7d84..dba39674 100644 --- a/src-blazor/WasmDynamicLinq/Pages/Home.razor.cs +++ b/src-blazor/WasmDynamicLinq/Pages/Home.razor.cs @@ -1,126 +1,13 @@ using System.Linq.Dynamic.Core; -using System.Reflection; -using System.Reflection.Emit; namespace WasmDynamicLinq.Pages; public partial class Home { - public class ExampleBase { } - - private void Test() - { - // Define a dynamic assembly and module - AssemblyName assemblyName = new AssemblyName("DynamicAssembly"); - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect); - ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule"); - - // Define a public class named "DynamicType" - TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicType", TypeAttributes.Public); - - // Define a public field of type string named "MyField" - typeBuilder.DefineField("MyField", typeof(string), FieldAttributes.Public); - - //typeBuilder.DefineGenericParameters("MyField"); - - // Create the type - Type dynamicType = typeBuilder.CreateType(); - //if (types.Length > 0) - { - //dynamicType = dynamicType.MakeGenericType(typeof(string)); - } - - // Create an instance of the dynamic type - dynamic dynamicObject = Activator.CreateInstance(dynamicType)!; - - // Set the value of the field using reflection - FieldInfo fieldInfo = dynamicType.GetField("MyField", BindingFlags.Public | BindingFlags.Instance)!; - fieldInfo.SetValue(dynamicObject, "Hello, World!"); - - // Output the value - Console.WriteLine("Test Field Value: " + (string)fieldInfo.GetValue(dynamicObject)); - Console.WriteLine("Test Field Value Dynamic: " + dynamicObject.MyField); - } - - private void TestChatGPT() - { - // Define a dynamic assembly and module - AssemblyName assemblyName = new AssemblyName("DynamicAssembly"); - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); - ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule"); - - // Define a public generic class named "DynamicType`1" with one generic type parameter - TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicType`1", TypeAttributes.Public); - - // Define a generic parameter "T" - GenericTypeParameterBuilder genericParameter = typeBuilder.DefineGenericParameters("T")[0]; - - // Define a public field of type T named "MyField" - FieldBuilder fieldBuilder = typeBuilder.DefineField("MyField", genericParameter, FieldAttributes.Public); - - // Create the generic type - Type dynamicType = typeBuilder.CreateType(); - - // Make a closed generic type of "DynamicType" - Type closedGenericType = dynamicType.MakeGenericType(typeof(string)); - - // Create an instance of the closed generic type - dynamic dynamicObject = Activator.CreateInstance(closedGenericType)!; - - // Set the value of the field directly - FieldInfo fieldInfo = closedGenericType.GetField("MyField", BindingFlags.Public | BindingFlags.Instance)!; - fieldInfo.SetValue(dynamicObject, "Hello, World!"); - - // Get the value of the field directly - string fieldValue = (string)fieldInfo.GetValue(dynamicObject); - - // Output the value - Console.WriteLine("Field Value: " + fieldValue); - Console.WriteLine("Test Field Value Dynamic: " + dynamicObject.MyField); - } - - private void TestFail() - { - // Define a dynamic assembly and module - AssemblyName assemblyName = new AssemblyName("DynamicAssembly"); - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect); - ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule"); - - // Define a public class named "DynamicType" - TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicType", TypeAttributes.Public); - - // Define a public field of type string named "MyField" - typeBuilder.DefineField("MyField", typeof(string), FieldAttributes.Public); - - // Define Generic Parameter - typeBuilder.DefineGenericParameters("T0"); - - // Create the type - Type dynamicType = typeBuilder.CreateType(); - - // Make it generic - dynamicType = dynamicType.MakeGenericType(typeof(string)); - - // Create an instance of the dynamic type - dynamic dynamicObject = Activator.CreateInstance(dynamicType)!; - - // Set the value of the field using reflection - FieldInfo fieldInfo = dynamicType.GetField("MyField", BindingFlags.Public | BindingFlags.Instance)!; - fieldInfo.SetValue(dynamicObject, "Hello, World!"); // this throws exception - - // Output the value - Console.WriteLine("Test Field Value: " + (string)fieldInfo.GetValue(dynamicObject)); - Console.WriteLine("Test Field Value Dynamic: " + dynamicObject.MyField); - } - protected override void OnInitialized() { base.OnInitialized(); - Test(); - //TestChatGPT(); - //TestFail(); - var o = new Order(); dynamic od = o; Console.WriteLine(od.OrderId); @@ -150,13 +37,8 @@ protected override void OnInitialized() var fieldValue = f.GetValue(dynamicClass1); Console.WriteLine($"Field {f.Name} Reflection Value = {fieldValue}"); Console.WriteLine(((dynamic)dynamicClass1).Name__Field); - break; } - - - - var orders = new List { new Order @@ -194,7 +76,6 @@ protected override void OnInitialized() Console.WriteLine("DynamicClass = " + element["Id"]); - // Fail Console.WriteLine("dynamic = " + ((dynamic)element).Id); } } diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore2/Properties/AssemblyInfo.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore2/Properties/AssemblyInfo.cs index 02bb2587..d0d07229 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore2/Properties/AssemblyInfo.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore2/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -#if !(WINDOWS_APP || NETSTANDARD2_0) +#if !(NETSTANDARD2_0) [assembly: Guid("b467c675-c014-4be3-85b9-9578941d28c8")] #endif diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3/EFDynamicQueryableExtensions.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3/EFDynamicQueryableExtensions.cs index 4a38f4d4..1e3a5d6e 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3/EFDynamicQueryableExtensions.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3/EFDynamicQueryableExtensions.cs @@ -1102,7 +1102,7 @@ private static Expression OptimizeExpression(Expression expression) { var optimized = ExtensibilityPoint.QueryOptimizer(expression); -#if !(WINDOWS_APP45x || SILVERLIGHT) +#if !(SILVERLIGHT) if (optimized != expression) { TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Expression before : {0}", expression); @@ -1116,4 +1116,4 @@ private static Expression OptimizeExpression(Expression expression) } #endregion Private Helpers } -} +} \ No newline at end of file diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3/Properties/AssemblyInfo.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3/Properties/AssemblyInfo.cs index 02bb2587..d0d07229 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3/Properties/AssemblyInfo.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -#if !(WINDOWS_APP || NETSTANDARD2_0) +#if !(NETSTANDARD2_0) [assembly: Guid("b467c675-c014-4be3-85b9-9578941d28c8")] #endif diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore5/Properties/AssemblyInfo.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore5/Properties/AssemblyInfo.cs index a54a04d9..409ab926 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore5/Properties/AssemblyInfo.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore5/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -#if !(WINDOWS_APP || NETSTANDARD2_1) +#if !(NETSTANDARD2_1) [assembly: Guid("b467c675-c014-4b55-85b9-9578941d28c8")] #endif diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore6/Properties/AssemblyInfo.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore6/Properties/AssemblyInfo.cs index a54a04d9..409ab926 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore6/Properties/AssemblyInfo.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore6/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -#if !(WINDOWS_APP || NETSTANDARD2_1) +#if !(NETSTANDARD2_1) [assembly: Guid("b467c675-c014-4b55-85b9-9578941d28c8")] #endif diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore7/Properties/AssemblyInfo.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore7/Properties/AssemblyInfo.cs index 54dd50af..d827f27c 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore7/Properties/AssemblyInfo.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore7/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -#if !(WINDOWS_APP || NETSTANDARD2_1) +#if !(NETSTANDARD2_1) [assembly: Guid("b467c675-c014-4b55-85b9-9578941d2ef7")] #endif diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Properties/AssemblyInfo.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Properties/AssemblyInfo.cs index 54dd50af..d827f27c 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Properties/AssemblyInfo.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -#if !(WINDOWS_APP || NETSTANDARD2_1) +#if !(NETSTANDARD2_1) [assembly: Guid("b467c675-c014-4b55-85b9-9578941d2ef7")] #endif diff --git a/src/System.Linq.Dynamic.Core/CustomTypeProviders/AbstractDynamicLinqCustomTypeProvider.cs b/src/System.Linq.Dynamic.Core/CustomTypeProviders/AbstractDynamicLinqCustomTypeProvider.cs index 2166a53a..3f54beed 100644 --- a/src/System.Linq.Dynamic.Core/CustomTypeProviders/AbstractDynamicLinqCustomTypeProvider.cs +++ b/src/System.Linq.Dynamic.Core/CustomTypeProviders/AbstractDynamicLinqCustomTypeProvider.cs @@ -78,7 +78,7 @@ protected IEnumerable FindTypesMarkedWithDynamicLinqTypeAttribute(IEnumera return null; } -#if (WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if (UAP10_0 || NETSTANDARD) /// /// Gets the assembly types annotated with in an Exception friendly way. /// diff --git a/src/System.Linq.Dynamic.Core/DefaultAssemblyHelper.cs b/src/System.Linq.Dynamic.Core/DefaultAssemblyHelper.cs index 0dcabe16..0c081407 100644 --- a/src/System.Linq.Dynamic.Core/DefaultAssemblyHelper.cs +++ b/src/System.Linq.Dynamic.Core/DefaultAssemblyHelper.cs @@ -11,7 +11,7 @@ internal class DefaultAssemblyHelper(ParsingConfig parsingConfig) : IAssemblyHel public Assembly[] GetAssemblies() { -#if WINDOWS_APP || UAP10_0 || NETSTANDARD || WPSL +#if UAP10_0 || NETSTANDARD || WPSL throw new NotSupportedException(); #elif NET35 return AppDomain.CurrentDomain.GetAssemblies(); diff --git a/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs b/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs index 1719d07d..d4ce7065 100644 --- a/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs +++ b/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs @@ -9,473 +9,486 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading; -#if WINDOWS_APP -using System.Linq; -#endif -namespace System.Linq.Dynamic.Core +namespace System.Linq.Dynamic.Core; + +/// +/// A factory to create dynamic classes, based on . +/// +public static class DynamicClassFactory { - /// - /// A factory to create dynamic classes, based on . - /// - public static class DynamicClassFactory - { - private const string DynamicAssemblyName = "System.Linq.Dynamic.Core.DynamicClasses, Version=1.0.0.0"; - private const string DynamicModuleName = "System.Linq.Dynamic.Core.DynamicClasses"; + private const string DynamicAssemblyName = "System.Linq.Dynamic.Core.DynamicClasses, Version=1.0.0.0"; + private const string DynamicModuleName = "System.Linq.Dynamic.Core.DynamicClasses"; - // Some objects we cache - private static readonly CustomAttributeBuilder CompilerGeneratedAttributeBuilder = new(typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes)!, []); - private static readonly CustomAttributeBuilder DebuggerBrowsableAttributeBuilder = new(typeof(DebuggerBrowsableAttribute).GetConstructor([typeof(DebuggerBrowsableState)])!, [DebuggerBrowsableState.Never]); - private static readonly CustomAttributeBuilder DebuggerHiddenAttributeBuilder = new(typeof(DebuggerHiddenAttribute).GetConstructor(Type.EmptyTypes)!, []); + // Some objects we cache + private static readonly CustomAttributeBuilder CompilerGeneratedAttributeBuilder = new(typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes)!, []); + private static readonly CustomAttributeBuilder DebuggerBrowsableAttributeBuilder = new(typeof(DebuggerBrowsableAttribute).GetConstructor([typeof(DebuggerBrowsableState)])!, [DebuggerBrowsableState.Never]); + private static readonly CustomAttributeBuilder DebuggerHiddenAttributeBuilder = new(typeof(DebuggerHiddenAttribute).GetConstructor(Type.EmptyTypes)!, []); - private static readonly ConstructorInfo ObjectCtor = typeof(object).GetConstructor(Type.EmptyTypes)!; + private static readonly ConstructorInfo ObjectCtor = typeof(object).GetConstructor(Type.EmptyTypes)!; -#if WINDOWS_APP || UAP10_0 || NETSTANDARD - private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public)!; +#if UAP10_0 || NETSTANDARD + private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public)!; #else - private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)!; + private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)!; #endif - private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes)!; -#if WINDOWS_APP || UAP10_0 || NETSTANDARD - private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), [typeof(string)])!; - private static readonly MethodInfo StringBuilderAppendObject = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), [typeof(object)])!; + private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes)!; +#if UAP10_0 || NETSTANDARD + private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), [typeof(string)])!; + private static readonly MethodInfo StringBuilderAppendObject = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), [typeof(object)])!; #else - private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), BindingFlags.Instance | BindingFlags.Public, null, [typeof(string)], null)!; - private static readonly MethodInfo StringBuilderAppendObject = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), BindingFlags.Instance | BindingFlags.Public, null, [typeof(object)], null)!; + private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), BindingFlags.Instance | BindingFlags.Public, null, [typeof(string)], null)!; + private static readonly MethodInfo StringBuilderAppendObject = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), BindingFlags.Instance | BindingFlags.Public, null, [typeof(object)], null)!; #endif - private static readonly Type EqualityComparer = typeof(EqualityComparer<>); + private static readonly Type EqualityComparer = typeof(EqualityComparer<>); - private static readonly ConcurrentDictionary GeneratedTypes = new(); - private static readonly ModuleBuilder ModuleBuilder; + private static readonly ConcurrentDictionary GeneratedTypes = new(); + private static readonly ModuleBuilder ModuleBuilder; - private static int _index = -1; + private static int _index = -1; - /// - /// Initializes the class. - /// - static DynamicClassFactory() - { - var assemblyName = new AssemblyName(DynamicAssemblyName); - var assemblyBuilder = AssemblyBuilderFactory.DefineDynamicAssembly - ( - assemblyName, + /// + /// Initializes the class. + /// + static DynamicClassFactory() + { + var assemblyName = new AssemblyName(DynamicAssemblyName); + var assemblyBuilder = AssemblyBuilderFactory.DefineDynamicAssembly + ( + assemblyName, #if NET35 - AssemblyBuilderAccess.Run + AssemblyBuilderAccess.Run #else - AssemblyBuilderAccess.RunAndCollect + AssemblyBuilderAccess.RunAndCollect #endif - ); + ); - ModuleBuilder = assemblyBuilder.DefineDynamicModule(DynamicModuleName); - } + ModuleBuilder = assemblyBuilder.DefineDynamicModule(DynamicModuleName); + } - /// - /// Create a GenericComparerType based on the GenericType and an instance of a . - /// - /// The GenericType - /// The instance - /// Type - public static Type CreateGenericComparerType(Type comparerGenericType, Type comparerType) - { - Check.NotNull(comparerGenericType); - Check.NotNull(comparerType); + /// + /// Create a GenericComparerType based on the GenericType and an instance of a . + /// + /// The GenericType + /// The instance + /// Type + public static Type CreateGenericComparerType(Type comparerGenericType, Type comparerType) + { + Check.NotNull(comparerGenericType); + Check.NotNull(comparerType); - var key = $"{comparerGenericType.FullName}_{comparerType.FullName}"; + var key = $"{comparerGenericType.FullName}_{comparerType.FullName}"; - // ReSharper disable once InconsistentlySynchronizedField - if (!GeneratedTypes.TryGetValue(key, out var type)) + // ReSharper disable once InconsistentlySynchronizedField + if (!GeneratedTypes.TryGetValue(key, out var type)) + { + // We create only a single class at a time, through this lock + // Note that this is a variant of the double-checked locking. + // It is safe because we are using a thread safe class. + lock (GeneratedTypes) { - // We create only a single class at a time, through this lock - // Note that this is a variant of the double-checked locking. - // It is safe because we are using a thread safe class. - lock (GeneratedTypes) + if (!GeneratedTypes.TryGetValue(key, out type)) { - if (!GeneratedTypes.TryGetValue(key, out type)) - { - var compareMethodGeneric = comparerGenericType.GetMethod("Compare")!; - var compareMethod = typeof(IComparer).GetMethod("Compare")!; - var compareCtor = comparerType.GetConstructor(Type.EmptyTypes)!; - var genericType = comparerGenericType.GetGenericArguments()[0]; - - var typeBuilder = ModuleBuilder.DefineType(key, TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoLayout, typeof(object)); - typeBuilder.AddInterfaceImplementation(comparerGenericType); - - var fieldBuilder = typeBuilder.DefineField("_c", typeof(IComparer), FieldAttributes.Private | FieldAttributes.InitOnly); - - var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, Type.EmptyTypes); - var constructorIL = constructorBuilder.GetILGenerator(); - constructorIL.Emit(OpCodes.Ldarg_0); - constructorIL.Emit(OpCodes.Call, ObjectCtor); - constructorIL.Emit(OpCodes.Ldarg_0); - constructorIL.Emit(OpCodes.Newobj, compareCtor); - constructorIL.Emit(OpCodes.Stfld, fieldBuilder); - constructorIL.Emit(OpCodes.Ret); - - var methodBuilder = typeBuilder.DefineMethod( - compareMethodGeneric.Name, - compareMethodGeneric.Attributes & ~MethodAttributes.Abstract, - compareMethodGeneric.CallingConvention, - compareMethodGeneric.ReturnType, - compareMethodGeneric.GetParameters().Select(p => p.ParameterType).ToArray() - ); - var methodBuilderIL = methodBuilder.GetILGenerator(); - methodBuilderIL.Emit(OpCodes.Ldarg_0); - methodBuilderIL.Emit(OpCodes.Ldfld, fieldBuilder); - methodBuilderIL.Emit(OpCodes.Ldarg_1); - methodBuilderIL.Emit(OpCodes.Box, genericType); - methodBuilderIL.Emit(OpCodes.Ldarg_2); - methodBuilderIL.Emit(OpCodes.Box, genericType); - methodBuilderIL.Emit(OpCodes.Callvirt, compareMethod); - methodBuilderIL.Emit(OpCodes.Ret); - - return GeneratedTypes.GetOrAdd(key, typeBuilder.CreateType()); - } + var compareMethodGeneric = comparerGenericType.GetMethod("Compare")!; + var compareMethod = typeof(IComparer).GetMethod("Compare")!; + var compareCtor = comparerType.GetConstructor(Type.EmptyTypes)!; + var genericType = comparerGenericType.GetGenericArguments()[0]; + + var typeBuilder = ModuleBuilder.DefineType(key, TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoLayout, typeof(object)); + typeBuilder.AddInterfaceImplementation(comparerGenericType); + + var fieldBuilder = typeBuilder.DefineField("_c", typeof(IComparer), FieldAttributes.Private | FieldAttributes.InitOnly); + + var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, Type.EmptyTypes); + var constructorIL = constructorBuilder.GetILGenerator(); + constructorIL.Emit(OpCodes.Ldarg_0); + constructorIL.Emit(OpCodes.Call, ObjectCtor); + constructorIL.Emit(OpCodes.Ldarg_0); + constructorIL.Emit(OpCodes.Newobj, compareCtor); + constructorIL.Emit(OpCodes.Stfld, fieldBuilder); + constructorIL.Emit(OpCodes.Ret); + + var methodBuilder = typeBuilder.DefineMethod( + compareMethodGeneric.Name, + compareMethodGeneric.Attributes & ~MethodAttributes.Abstract, + compareMethodGeneric.CallingConvention, + compareMethodGeneric.ReturnType, + compareMethodGeneric.GetParameters().Select(p => p.ParameterType).ToArray() + ); + var methodBuilderIL = methodBuilder.GetILGenerator(); + methodBuilderIL.Emit(OpCodes.Ldarg_0); + methodBuilderIL.Emit(OpCodes.Ldfld, fieldBuilder); + methodBuilderIL.Emit(OpCodes.Ldarg_1); + methodBuilderIL.Emit(OpCodes.Box, genericType); + methodBuilderIL.Emit(OpCodes.Ldarg_2); + methodBuilderIL.Emit(OpCodes.Box, genericType); + methodBuilderIL.Emit(OpCodes.Callvirt, compareMethod); + methodBuilderIL.Emit(OpCodes.Ret); + + return GeneratedTypes.GetOrAdd(key, typeBuilder.CreateType()); } } - - return type; } - /// - /// The CreateType method creates a new data class with a given set of public properties and returns the System.Type object for the newly created class. If a data class with an identical sequence of properties has already been created, the System.Type object for this class is returned. - /// Data classes implement private instance variables and read/write property accessors for the specified properties.Data classes also override the Equals and GetHashCode members to implement by-value equality. - /// Data classes are created in an in-memory assembly in the current application domain. All data classes inherit from and are given automatically generated names that should be considered private (the names will be unique within the application domain but not across multiple invocations of the application). Note that once created, a data class stays in memory for the lifetime of the current application domain. There is currently no way to unload a dynamically created data class. - /// The dynamic expression parser uses the CreateClass methods to generate classes from data object initializers. This feature in turn is often used with the dynamic Select method to create projections. - /// - /// The DynamicProperties - /// Create a constructor with parameters. Default set to true. Note that for Linq-to-Database objects, this needs to be set to false. - /// Type - /// - /// - /// - /// - /// - public static Type CreateType(IList properties, bool createParameterCtor = true) - { - Check.HasNoNulls(properties); + return type; + } + + /// + /// The CreateType method creates a new data class with a given set of public properties and returns the System.Type object for the newly created class. If a data class with an identical sequence of properties has already been created, the System.Type object for this class is returned. + /// Data classes implement private instance variables and read/write property accessors for the specified properties.Data classes also override the Equals and GetHashCode members to implement by-value equality. + /// Data classes are created in an in-memory assembly in the current application domain. All data classes inherit from and are given automatically generated names that should be considered private (the names will be unique within the application domain but not across multiple invocations of the application). Note that once created, a data class stays in memory for the lifetime of the current application domain. There is currently no way to unload a dynamically created data class. + /// The dynamic expression parser uses the CreateClass methods to generate classes from data object initializers. This feature in turn is often used with the dynamic Select method to create projections. + /// + /// The DynamicProperties + /// Create a constructor with parameters. Default set to true. Note that for Linq-to-Database objects, this needs to be set to false. + /// Type + /// + /// + /// + /// + /// + public static Type CreateType(IList properties, bool createParameterCtor = true) + { + Check.HasNoNulls(properties); - var key = GenerateKey(properties, createParameterCtor); + var key = GenerateKey(properties, createParameterCtor); - // ReSharper disable once InconsistentlySynchronizedField - if (!GeneratedTypes.TryGetValue(key, out var type)) + // ReSharper disable once InconsistentlySynchronizedField + if (!GeneratedTypes.TryGetValue(key, out var type)) + { + // We create only a single class at a time, through this lock. + // Note that this is a variant of the double-checked locking. + // It is safe because we are using a thread safe class. + lock (GeneratedTypes) { - // We create only a single class at a time, through this lock. - // Note that this is a variant of the double-checked locking. - // It is safe because we are using a thread safe class. - lock (GeneratedTypes) - { - return GeneratedTypes.GetOrAdd(key, _ => EmitType(properties, createParameterCtor)); - } + return GeneratedTypes.GetOrAdd(key, _ => EmitType(properties, createParameterCtor)); } - - return type; } - private static Type EmitType(IList properties, bool createParameterCtor) - { - var typeIndex = Interlocked.Increment(ref _index); - var typeName = properties.Any() ? $"<>f__AnonymousType{typeIndex}`{properties.Count}" : $"<>f__AnonymousType{typeIndex}"; + return type; + } - var typeBuilder = ModuleBuilder.DefineType(typeName, TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.BeforeFieldInit, typeof(DynamicClass)); - typeBuilder.SetCustomAttribute(CompilerGeneratedAttributeBuilder); + private static Type EmitType(IList properties, bool createParameterCtor) + { + var typeIndex = Interlocked.Increment(ref _index); + var typeName = properties.Any() ? $"<>f__AnonymousType{typeIndex}`{properties.Count}" : $"<>f__AnonymousType{typeIndex}"; - var fieldBuilders = new FieldBuilder[properties.Count]; + var typeBuilder = ModuleBuilder.DefineType(typeName, TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.BeforeFieldInit, typeof(DynamicClass)); + typeBuilder.SetCustomAttribute(CompilerGeneratedAttributeBuilder); - // There are two for-loops because we want to have all the getter methods before all the other methods - for (int i = 0; i < properties.Count; i++) - { - var fieldName = properties[i].Name; - var fieldType = properties[i].Type; - - // field - fieldBuilders[i] = typeBuilder.DefineField($"<{fieldName}>i__Field", fieldType, FieldAttributes.Private | FieldAttributes.InitOnly); - fieldBuilders[i].SetCustomAttribute(DebuggerBrowsableAttributeBuilder); - - PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(fieldName, PropertyAttributes.None, CallingConventions.HasThis, fieldType, Type.EmptyTypes); - - // getter - MethodBuilder getter = typeBuilder.DefineMethod($"get_{fieldName}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, CallingConventions.HasThis, fieldType, null); - getter.SetCustomAttribute(CompilerGeneratedAttributeBuilder); - - ILGenerator ilgeneratorGetter = getter.GetILGenerator(); - ilgeneratorGetter.Emit(OpCodes.Ldarg_0); - ilgeneratorGetter.Emit(OpCodes.Ldfld, fieldBuilders[i]); - ilgeneratorGetter.Emit(OpCodes.Ret); - propertyBuilder.SetGetMethod(getter); - - // setter - MethodBuilder setter = typeBuilder.DefineMethod($"set_{fieldName}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, CallingConventions.HasThis, null, [fieldType]); - setter.SetCustomAttribute(CompilerGeneratedAttributeBuilder); - - // workaround for https://github.com/dotnet/corefx/issues/7792 - setter.DefineParameter(1, ParameterAttributes.In, properties[i].Name); - - ILGenerator ilgeneratorSetter = setter.GetILGenerator(); - ilgeneratorSetter.Emit(OpCodes.Ldarg_0); - ilgeneratorSetter.Emit(OpCodes.Ldarg_1); - ilgeneratorSetter.Emit(OpCodes.Stfld, fieldBuilders[i]); - ilgeneratorSetter.Emit(OpCodes.Ret); - propertyBuilder.SetSetMethod(setter); - } + var fieldBuilders = new FieldBuilder[properties.Count]; - // ToString() - MethodBuilder toString = typeBuilder.DefineMethod(nameof(ToString), MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(string), Type.EmptyTypes); - toString.SetCustomAttribute(DebuggerHiddenAttributeBuilder); - - ILGenerator ilgeneratorToString = toString.GetILGenerator(); - ilgeneratorToString.DeclareLocal(typeof(StringBuilder)); - ilgeneratorToString.Emit(OpCodes.Newobj, StringBuilderCtor); - ilgeneratorToString.Emit(OpCodes.Stloc_0); - - // Equals - MethodBuilder equals = typeBuilder.DefineMethod(nameof(Equals), MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(bool), [typeof(object)]); - equals.DefineParameter(1, ParameterAttributes.In, "value"); - equals.SetCustomAttribute(DebuggerHiddenAttributeBuilder); - - ILGenerator ilgeneratorEquals = equals.GetILGenerator(); - ilgeneratorEquals.DeclareLocal(typeBuilder.AsType()); - ilgeneratorEquals.Emit(OpCodes.Ldarg_1); - ilgeneratorEquals.Emit(OpCodes.Isinst, typeBuilder.AsType()); - ilgeneratorEquals.Emit(OpCodes.Stloc_0); - ilgeneratorEquals.Emit(OpCodes.Ldloc_0); + // There are two for-loops because we want to have all the getter methods before all the other methods + for (int i = 0; i < properties.Count; i++) + { + var fieldName = properties[i].Name; + var fieldType = properties[i].Type; + + // field + fieldBuilders[i] = typeBuilder.DefineField($"<{fieldName}>i__Field", fieldType, FieldAttributes.Private | FieldAttributes.InitOnly); + fieldBuilders[i].SetCustomAttribute(DebuggerBrowsableAttributeBuilder); + + PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(fieldName, PropertyAttributes.None, CallingConventions.HasThis, fieldType, Type.EmptyTypes); + + // getter + MethodBuilder getter = typeBuilder.DefineMethod($"get_{fieldName}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, CallingConventions.HasThis, fieldType, null); + getter.SetCustomAttribute(CompilerGeneratedAttributeBuilder); + + ILGenerator ilgeneratorGetter = getter.GetILGenerator(); + ilgeneratorGetter.Emit(OpCodes.Ldarg_0); + ilgeneratorGetter.Emit(OpCodes.Ldfld, fieldBuilders[i]); + ilgeneratorGetter.Emit(OpCodes.Ret); + propertyBuilder.SetGetMethod(getter); + + // setter + MethodBuilder setter = typeBuilder.DefineMethod($"set_{fieldName}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, CallingConventions.HasThis, null, [fieldType]); + setter.SetCustomAttribute(CompilerGeneratedAttributeBuilder); + + // workaround for https://github.com/dotnet/corefx/issues/7792 + setter.DefineParameter(1, ParameterAttributes.In, properties[i].Name); + + ILGenerator ilgeneratorSetter = setter.GetILGenerator(); + ilgeneratorSetter.Emit(OpCodes.Ldarg_0); + ilgeneratorSetter.Emit(OpCodes.Ldarg_1); + ilgeneratorSetter.Emit(OpCodes.Stfld, fieldBuilders[i]); + ilgeneratorSetter.Emit(OpCodes.Ret); + propertyBuilder.SetSetMethod(setter); + } - // GetHashCode() - MethodBuilder getHashCode = typeBuilder.DefineMethod(nameof(GetHashCode), MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(int), Type.EmptyTypes); - getHashCode.SetCustomAttribute(DebuggerHiddenAttributeBuilder); + // ToString() + MethodBuilder toString = typeBuilder.DefineMethod(nameof(ToString), MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(string), Type.EmptyTypes); + toString.SetCustomAttribute(DebuggerHiddenAttributeBuilder); - ILGenerator ilgeneratorGetHashCode = getHashCode.GetILGenerator(); - ilgeneratorGetHashCode.DeclareLocal(typeof(int)); + ILGenerator ilgeneratorToString = toString.GetILGenerator(); + ilgeneratorToString.DeclareLocal(typeof(StringBuilder)); + ilgeneratorToString.Emit(OpCodes.Newobj, StringBuilderCtor); + ilgeneratorToString.Emit(OpCodes.Stloc_0); - if (properties.Count == 0) - { - ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4_0); - } - else - { - // As done by Roslyn - // Note that initHash can vary, because string.GetHashCode() isn't "stable" for different compilation of the code - int initHash = 0; + // Equals + MethodBuilder equals = typeBuilder.DefineMethod(nameof(Equals), MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(bool), [typeof(object)]); + equals.DefineParameter(1, ParameterAttributes.In, "value"); + equals.SetCustomAttribute(DebuggerHiddenAttributeBuilder); - for (int i = 0; i < properties.Count; i++) - { - initHash = unchecked(initHash * -1521134295 + fieldBuilders[i].Name.GetHashCode()); - } + ILGenerator ilgeneratorEquals = equals.GetILGenerator(); + ilgeneratorEquals.DeclareLocal(typeBuilder.AsType()); + ilgeneratorEquals.Emit(OpCodes.Ldarg_1); + ilgeneratorEquals.Emit(OpCodes.Isinst, typeBuilder.AsType()); + ilgeneratorEquals.Emit(OpCodes.Stloc_0); + ilgeneratorEquals.Emit(OpCodes.Ldloc_0); - // Note that the CSC seems to generate a different seed for every anonymous class - ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, initHash); - } + // GetHashCode() + MethodBuilder getHashCode = typeBuilder.DefineMethod(nameof(GetHashCode), MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(int), Type.EmptyTypes); + getHashCode.SetCustomAttribute(DebuggerHiddenAttributeBuilder); - Label equalsLabel = ilgeneratorEquals.DefineLabel(); + ILGenerator ilgeneratorGetHashCode = getHashCode.GetILGenerator(); + ilgeneratorGetHashCode.DeclareLocal(typeof(int)); - for (var i = 0; i < properties.Count; i++) - { - var fieldName = properties[i].Name; - var fieldType = properties[i].Type; - var equalityComparerT = EqualityComparer.MakeGenericType(fieldType); - - // Equals() - MethodInfo equalityComparerTDefault = equalityComparerT.GetMethod("get_Default", BindingFlags.Static | BindingFlags.Public)!; - MethodInfo equalityComparerTEquals = equalityComparerT.GetMethod(nameof(EqualityComparer.Equals), BindingFlags.Instance | BindingFlags.Public, null, [fieldType, fieldType], null)!; - - // Illegal one-byte branch at position: 9. Requested branch was: 143. - // So replace OpCodes.Brfalse_S to OpCodes.Brfalse - ilgeneratorEquals.Emit(OpCodes.Brfalse, equalsLabel); - ilgeneratorEquals.Emit(OpCodes.Call, equalityComparerTDefault); - ilgeneratorEquals.Emit(OpCodes.Ldarg_0); - ilgeneratorEquals.Emit(OpCodes.Ldfld, fieldBuilders[i]); - ilgeneratorEquals.Emit(OpCodes.Ldloc_0); - ilgeneratorEquals.Emit(OpCodes.Ldfld, fieldBuilders[i]); - ilgeneratorEquals.Emit(OpCodes.Callvirt, equalityComparerTEquals); - - // GetHashCode(); - MethodInfo equalityComparerTGetHashCode = equalityComparerT.GetMethod(nameof(EqualityComparer.GetHashCode), BindingFlags.Instance | BindingFlags.Public, null, [fieldType], null)!; - ilgeneratorGetHashCode.Emit(OpCodes.Stloc_0); - ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, -1521134295); - ilgeneratorGetHashCode.Emit(OpCodes.Ldloc_0); - ilgeneratorGetHashCode.Emit(OpCodes.Mul); - ilgeneratorGetHashCode.Emit(OpCodes.Call, equalityComparerTDefault); - ilgeneratorGetHashCode.Emit(OpCodes.Ldarg_0); - ilgeneratorGetHashCode.Emit(OpCodes.Ldfld, fieldBuilders[i]); - ilgeneratorGetHashCode.Emit(OpCodes.Callvirt, equalityComparerTGetHashCode); - ilgeneratorGetHashCode.Emit(OpCodes.Add); - - // ToString(); - ilgeneratorToString.Emit(OpCodes.Ldloc_0); - ilgeneratorToString.Emit(OpCodes.Ldstr, i == 0 ? $"{{ {fieldName} = " : $", {fieldName} = "); - ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString); - ilgeneratorToString.Emit(OpCodes.Pop); - ilgeneratorToString.Emit(OpCodes.Ldloc_0); - ilgeneratorToString.Emit(OpCodes.Ldarg_0); - ilgeneratorToString.Emit(OpCodes.Ldfld, fieldBuilders[i]); - ilgeneratorToString.Emit(OpCodes.Box, properties[i].Type); - ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendObject); - ilgeneratorToString.Emit(OpCodes.Pop); - } + if (properties.Count == 0) + { + ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4_0); + } + else + { + // As done by Roslyn + // Note that initHash can vary, because string.GetHashCode() isn't "stable" for different compilation of the code + int initHash = 0; - // Only create the default and with params constructor when there are any params. - // Otherwise default constructor is not needed because it matches the default - // one provided by the runtime when no constructor is present - if (createParameterCtor && properties.Any()) + for (int i = 0; i < properties.Count; i++) { - // .ctor default - ConstructorBuilder constructorDef = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, Type.EmptyTypes); - constructorDef.SetCustomAttribute(DebuggerHiddenAttributeBuilder); - - ILGenerator ilgeneratorConstructorDef = constructorDef.GetILGenerator(); - ilgeneratorConstructorDef.Emit(OpCodes.Ldarg_0); - ilgeneratorConstructorDef.Emit(OpCodes.Call, ObjectCtor); - ilgeneratorConstructorDef.Emit(OpCodes.Ret); - - // .ctor with params - var types = properties.Select(p => p.Type).ToArray(); - ConstructorBuilder constructor = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, types); - constructor.SetCustomAttribute(DebuggerHiddenAttributeBuilder); + initHash = unchecked(initHash * -1521134295 + fieldBuilders[i].Name.GetHashCode()); + } - ILGenerator ilgeneratorConstructor = constructor.GetILGenerator(); - ilgeneratorConstructor.Emit(OpCodes.Ldarg_0); - ilgeneratorConstructor.Emit(OpCodes.Call, ObjectCtor); + // Note that the CSC seems to generate a different seed for every anonymous class + ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, initHash); + } - for (var i = 0; i < properties.Count; i++) - { - constructor.DefineParameter(i + 1, ParameterAttributes.None, properties[i].Name); - ilgeneratorConstructor.Emit(OpCodes.Ldarg_0); - - if (i == 0) - { - ilgeneratorConstructor.Emit(OpCodes.Ldarg_1); - } - else if (i == 1) - { - ilgeneratorConstructor.Emit(OpCodes.Ldarg_2); - } - else if (i == 2) - { - ilgeneratorConstructor.Emit(OpCodes.Ldarg_3); - } - else if (i < 255) - { - ilgeneratorConstructor.Emit(OpCodes.Ldarg_S, (byte)(i + 1)); - } - else - { - // Ldarg uses an ushort, but the Emit only accepts short, so we use a unchecked(...), cast to short and let the CLR interpret it as ushort. - ilgeneratorConstructor.Emit(OpCodes.Ldarg, unchecked((short)(i + 1))); - } - - ilgeneratorConstructor.Emit(OpCodes.Stfld, fieldBuilders[i]); - } + Label equalsLabel = ilgeneratorEquals.DefineLabel(); - ilgeneratorConstructor.Emit(OpCodes.Ret); - } + for (var i = 0; i < properties.Count; i++) + { + var fieldName = properties[i].Name; + var fieldType = properties[i].Type; + var equalityComparerT = EqualityComparer.MakeGenericType(fieldType); // Equals() - if (properties.Count == 0) - { - ilgeneratorEquals.Emit(OpCodes.Ldnull); - ilgeneratorEquals.Emit(OpCodes.Ceq); - ilgeneratorEquals.Emit(OpCodes.Ldc_I4_0); - ilgeneratorEquals.Emit(OpCodes.Ceq); - } - else - { - ilgeneratorEquals.Emit(OpCodes.Ret); - ilgeneratorEquals.MarkLabel(equalsLabel); - ilgeneratorEquals.Emit(OpCodes.Ldc_I4_0); - } - - ilgeneratorEquals.Emit(OpCodes.Ret); + MethodInfo equalityComparerTDefault = equalityComparerT.GetMethod("get_Default", BindingFlags.Static | BindingFlags.Public)!; + MethodInfo equalityComparerTEquals = equalityComparerT.GetMethod(nameof(EqualityComparer.Equals), BindingFlags.Instance | BindingFlags.Public, null, [fieldType, fieldType], null)!; + + // Illegal one-byte branch at position: 9. Requested branch was: 143. + // So replace OpCodes.Brfalse_S to OpCodes.Brfalse + ilgeneratorEquals.Emit(OpCodes.Brfalse, equalsLabel); + ilgeneratorEquals.Emit(OpCodes.Call, equalityComparerTDefault); + ilgeneratorEquals.Emit(OpCodes.Ldarg_0); + ilgeneratorEquals.Emit(OpCodes.Ldfld, fieldBuilders[i]); + ilgeneratorEquals.Emit(OpCodes.Ldloc_0); + ilgeneratorEquals.Emit(OpCodes.Ldfld, fieldBuilders[i]); + ilgeneratorEquals.Emit(OpCodes.Callvirt, equalityComparerTEquals); - // GetHashCode() + // GetHashCode(); + MethodInfo equalityComparerTGetHashCode = equalityComparerT.GetMethod(nameof(EqualityComparer.GetHashCode), BindingFlags.Instance | BindingFlags.Public, null, [fieldType], null)!; ilgeneratorGetHashCode.Emit(OpCodes.Stloc_0); + ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, -1521134295); ilgeneratorGetHashCode.Emit(OpCodes.Ldloc_0); - ilgeneratorGetHashCode.Emit(OpCodes.Ret); - - // ToString() + ilgeneratorGetHashCode.Emit(OpCodes.Mul); + ilgeneratorGetHashCode.Emit(OpCodes.Call, equalityComparerTDefault); + ilgeneratorGetHashCode.Emit(OpCodes.Ldarg_0); + ilgeneratorGetHashCode.Emit(OpCodes.Ldfld, fieldBuilders[i]); + ilgeneratorGetHashCode.Emit(OpCodes.Callvirt, equalityComparerTGetHashCode); + ilgeneratorGetHashCode.Emit(OpCodes.Add); + + // ToString(); ilgeneratorToString.Emit(OpCodes.Ldloc_0); - ilgeneratorToString.Emit(OpCodes.Ldstr, properties.Count == 0 ? "{ }" : " }"); + ilgeneratorToString.Emit(OpCodes.Ldstr, i == 0 ? $"{{ {fieldName} = " : $", {fieldName} = "); ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString); ilgeneratorToString.Emit(OpCodes.Pop); ilgeneratorToString.Emit(OpCodes.Ldloc_0); - ilgeneratorToString.Emit(OpCodes.Callvirt, ObjectToString); - ilgeneratorToString.Emit(OpCodes.Ret); - - EmitEqualityOperator(typeBuilder, equals); - - return typeBuilder.CreateType(); + ilgeneratorToString.Emit(OpCodes.Ldarg_0); + ilgeneratorToString.Emit(OpCodes.Ldfld, fieldBuilders[i]); + ilgeneratorToString.Emit(OpCodes.Box, properties[i].Type); + ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendObject); + ilgeneratorToString.Emit(OpCodes.Pop); } - private static void EmitEqualityOperator(TypeBuilder typeBuilder, MethodBuilder equals) + // Only create the default and with params constructor when there are any params. + // Otherwise default constructor is not needed because it matches the default + // one provided by the runtime when no constructor is present + if (createParameterCtor && properties.Any()) { - // Define the '==' operator - MethodBuilder equalityOperator = typeBuilder.DefineMethod( - "op_Equality", - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.HideBySig, - typeof(bool), - [typeBuilder.AsType(), typeBuilder.AsType()]); - - ILGenerator ilgeneratorEqualityOperator = equalityOperator.GetILGenerator(); - - // if (left == null || right == null) return ReferenceEquals(left, right); - Label endLabel = ilgeneratorEqualityOperator.DefineLabel(); - ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_0); - ilgeneratorEqualityOperator.Emit(OpCodes.Brfalse_S, endLabel); - ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_1); - ilgeneratorEqualityOperator.Emit(OpCodes.Brfalse_S, endLabel); - - // return left.Equals(right); - ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_0); - ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_1); - ilgeneratorEqualityOperator.Emit(OpCodes.Callvirt, equals); - ilgeneratorEqualityOperator.Emit(OpCodes.Ret); - - // Return false if one is null - ilgeneratorEqualityOperator.MarkLabel(endLabel); - ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_0); - ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_1); - ilgeneratorEqualityOperator.Emit(OpCodes.Call, typeof(object).GetMethod("ReferenceEquals")!); - ilgeneratorEqualityOperator.Emit(OpCodes.Ret); - } + // .ctor default + ConstructorBuilder constructorDef = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, Type.EmptyTypes); + constructorDef.SetCustomAttribute(DebuggerHiddenAttributeBuilder); - /// - /// Used for unit-testing - /// - internal static void ClearGeneratedTypes() - { - lock (GeneratedTypes) + ILGenerator ilgeneratorConstructorDef = constructorDef.GetILGenerator(); + ilgeneratorConstructorDef.Emit(OpCodes.Ldarg_0); + ilgeneratorConstructorDef.Emit(OpCodes.Call, ObjectCtor); + ilgeneratorConstructorDef.Emit(OpCodes.Ret); + + // .ctor with params + var types = properties.Select(p => p.Type).ToArray(); + ConstructorBuilder constructor = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, types); + constructor.SetCustomAttribute(DebuggerHiddenAttributeBuilder); + + ILGenerator ilgeneratorConstructor = constructor.GetILGenerator(); + ilgeneratorConstructor.Emit(OpCodes.Ldarg_0); + ilgeneratorConstructor.Emit(OpCodes.Call, ObjectCtor); + + for (var i = 0; i < properties.Count; i++) { - GeneratedTypes.Clear(); + constructor.DefineParameter(i + 1, ParameterAttributes.None, properties[i].Name); + ilgeneratorConstructor.Emit(OpCodes.Ldarg_0); + + if (i == 0) + { + ilgeneratorConstructor.Emit(OpCodes.Ldarg_1); + } + else if (i == 1) + { + ilgeneratorConstructor.Emit(OpCodes.Ldarg_2); + } + else if (i == 2) + { + ilgeneratorConstructor.Emit(OpCodes.Ldarg_3); + } + else if (i < 255) + { + ilgeneratorConstructor.Emit(OpCodes.Ldarg_S, (byte)(i + 1)); + } + else + { + // Ldarg uses an ushort, but the Emit only accepts short, so we use a unchecked(...), cast to short and let the CLR interpret it as ushort. + ilgeneratorConstructor.Emit(OpCodes.Ldarg, unchecked((short)(i + 1))); + } + + ilgeneratorConstructor.Emit(OpCodes.Stfld, fieldBuilders[i]); } + + ilgeneratorConstructor.Emit(OpCodes.Ret); } - /// - /// Generates the key. - /// Anonymous classes are generics based. The generic classes are distinguished by number of parameters and name of parameters. - /// The specific types of the parameters are the generic arguments. - /// - /// The dynamic properties. - /// if set to true [create parameter ctor]. - /// - private static string GenerateKey(IEnumerable dynamicProperties, bool createParameterCtor) + // Equals() + if (properties.Count == 0) + { + ilgeneratorEquals.Emit(OpCodes.Ldnull); + ilgeneratorEquals.Emit(OpCodes.Ceq); + ilgeneratorEquals.Emit(OpCodes.Ldc_I4_0); + ilgeneratorEquals.Emit(OpCodes.Ceq); + } + else { - // We recreate this by creating a fullName composed of all the property names and types, separated by a "|". - // And append and extra field depending on createParameterCtor. - return $"{string.Join("|", dynamicProperties.Select(p => Escape(p.Name) + "~" + p.Type.FullName).ToArray())}_{(createParameterCtor ? "c" : string.Empty)}"; + ilgeneratorEquals.Emit(OpCodes.Ret); + ilgeneratorEquals.MarkLabel(equalsLabel); + ilgeneratorEquals.Emit(OpCodes.Ldc_I4_0); } - private static string Escape(string str) + ilgeneratorEquals.Emit(OpCodes.Ret); + + // GetHashCode() + ilgeneratorGetHashCode.Emit(OpCodes.Stloc_0); + ilgeneratorGetHashCode.Emit(OpCodes.Ldloc_0); + ilgeneratorGetHashCode.Emit(OpCodes.Ret); + + // ToString() + ilgeneratorToString.Emit(OpCodes.Ldloc_0); + ilgeneratorToString.Emit(OpCodes.Ldstr, properties.Count == 0 ? "{ }" : " }"); + ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString); + ilgeneratorToString.Emit(OpCodes.Pop); + ilgeneratorToString.Emit(OpCodes.Ldloc_0); + ilgeneratorToString.Emit(OpCodes.Callvirt, ObjectToString); + ilgeneratorToString.Emit(OpCodes.Ret); + + EmitEqualityOperators(typeBuilder, equals); + + return typeBuilder.CreateType(); + } + + private static void EmitEqualityOperators(TypeBuilder typeBuilder, MethodBuilder equals) + { + // Define the '==' operator + MethodBuilder equalityOperator = typeBuilder.DefineMethod( + "op_Equality", + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + typeof(bool), + [typeBuilder.AsType(), typeBuilder.AsType()]); + + ILGenerator ilgeneratorEqualityOperator = equalityOperator.GetILGenerator(); + + // if (left == null || right == null) return ReferenceEquals(left, right); + Label endLabel = ilgeneratorEqualityOperator.DefineLabel(); + ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_0); + ilgeneratorEqualityOperator.Emit(OpCodes.Brfalse_S, endLabel); + ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_1); + ilgeneratorEqualityOperator.Emit(OpCodes.Brfalse_S, endLabel); + + // return left.Equals(right); + ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_0); + ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_1); + ilgeneratorEqualityOperator.Emit(OpCodes.Callvirt, equals); + ilgeneratorEqualityOperator.Emit(OpCodes.Ret); + + // Return false if one is null + ilgeneratorEqualityOperator.MarkLabel(endLabel); + ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_0); + ilgeneratorEqualityOperator.Emit(OpCodes.Ldarg_1); + ilgeneratorEqualityOperator.Emit(OpCodes.Call, typeof(object).GetMethod("ReferenceEquals")!); + ilgeneratorEqualityOperator.Emit(OpCodes.Ret); + + // Define the '!=' operator + MethodBuilder inequalityOperator = typeBuilder.DefineMethod( + "op_Inequality", + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + typeof(bool), + [typeBuilder.AsType(), typeBuilder.AsType()]); + + ILGenerator ilNeq = inequalityOperator.GetILGenerator(); + + // return !(left == right); + ilNeq.Emit(OpCodes.Ldarg_0); + ilNeq.Emit(OpCodes.Ldarg_1); + ilNeq.Emit(OpCodes.Call, equalityOperator); + ilNeq.Emit(OpCodes.Ldc_I4_0); + ilNeq.Emit(OpCodes.Ceq); + ilNeq.Emit(OpCodes.Ret); + } + + /// + /// Generates the key. + /// Anonymous classes are generics based. The generic classes are distinguished by number of parameters and name of parameters. + /// The specific types of the parameters are the generic arguments. + /// + /// The dynamic properties. + /// if set to true [create parameter ctor]. + /// + private static string GenerateKey(IEnumerable dynamicProperties, bool createParameterCtor) + { + // We recreate this by creating a fullName composed of all the property names and types, separated by a "|". + // And append and extra field depending on createParameterCtor. + return $"{string.Join("|", dynamicProperties.Select(p => Escape(p.Name) + "~" + p.Type.FullName).ToArray())}_{(createParameterCtor ? "c" : string.Empty)}"; + } + + private static string Escape(string str) + { + // We escape the \ with \\, so that we can safely escape the "|" (that we use as a separator) with "\|" + str = str.Replace(@"\", @"\\"); + str = str.Replace(@"|", @"\|"); + return str; + } + + /// + /// Used for unit-testing + /// + internal static void ClearGeneratedTypes() + { + lock (GeneratedTypes) { - // We escape the \ with \\, so that we can safely escape the "|" (that we use as a separator) with "\|" - str = str.Replace(@"\", @"\\"); - str = str.Replace(@"|", @"\|"); - return str; + GeneratedTypes.Clear(); } } } diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index cf63c189..3c4d21bb 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -3,19 +3,14 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq.Dynamic.Core.Exceptions; -#if !(WINDOWS_APP45x || SILVERLIGHT) -using System.Diagnostics; -#endif using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; using System.Linq.Dynamic.Core.Parser; using System.Linq.Dynamic.Core.Util; - -#if WINDOWS_APP -using System; -using System.Linq; +#if !(SILVERLIGHT) +using System.Diagnostics; #endif namespace System.Linq.Dynamic.Core @@ -28,7 +23,7 @@ namespace System.Linq.Dynamic.Core [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] public static class DynamicQueryableExtensions { -#if !(WINDOWS_APP45x || SILVERLIGHT) +#if !(SILVERLIGHT) private static readonly TraceSource TraceSource = new(nameof(DynamicQueryableExtensions)); #endif @@ -38,7 +33,7 @@ private static Expression OptimizeExpression(Expression expression) { var optimized = ExtensibilityPoint.QueryOptimizer(expression); -#if !(WINDOWS_APP45x || SILVERLIGHT) +#if !(SILVERLIGHT) if (optimized != expression) { TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Expression before : {0}", expression); diff --git a/src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs b/src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs index 404a1b91..fba8b3d1 100644 --- a/src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs +++ b/src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs @@ -9,7 +9,7 @@ namespace System.Linq.Dynamic.Core.Exceptions /// /// Represents errors that occur while parsing dynamic linq string expressions. /// -#if !(SILVERLIGHT || WINDOWS_APP || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0) +#if !(SILVERLIGHT || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0) [Serializable] #endif public sealed class ParseException : Exception @@ -51,7 +51,7 @@ public override string ToString() return text; } -#if !(SILVERLIGHT || WINDOWS_APP || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0) +#if !(SILVERLIGHT || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0) private ParseException(SerializationInfo info, StreamingContext context) : base(info, context) { Position = (int)info.GetValue("position", typeof(int)); diff --git a/src/System.Linq.Dynamic.Core/ExtensibilityPoint.cs b/src/System.Linq.Dynamic.Core/ExtensibilityPoint.cs index af3fac75..45abdebd 100644 --- a/src/System.Linq.Dynamic.Core/ExtensibilityPoint.cs +++ b/src/System.Linq.Dynamic.Core/ExtensibilityPoint.cs @@ -1,17 +1,16 @@ using System.Linq.Expressions; -namespace System.Linq.Dynamic.Core +namespace System.Linq.Dynamic.Core; + +/// +/// Extensibility point: If you want to modify expanded queries before executing them +/// set your own functionality to override empty QueryOptimizer +/// +public class ExtensibilityPoint { /// - /// Extensibility point: If you want to modify expanded queries before executing them - /// set your own functionality to override empty QueryOptimizer + /// Place to optimize your queries. Example: Add a reference to Nuget package Linq.Expression.Optimizer + /// and in your program initializers set Extensibility.QueryOptimizer = ExpressionOptimizer.visit; /// - public class ExtensibilityPoint - { - /// - /// Place to optimize your queries. Example: Add a reference to Nuget package Linq.Expression.Optimizer - /// and in your program initializers set Extensibility.QueryOptimizer = ExpressionOptimizer.visit; - /// - public static Func QueryOptimizer = e => e; - } + public static Func QueryOptimizer = e => e; } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 9262ad49..09f2a191 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -2460,7 +2460,7 @@ private static Exception IncompatibleOperandsError(string opName, Expression lef private MemberInfo? FindPropertyOrField(Type type, string memberName, bool staticAccess) { -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(UAP10_0 || NETSTANDARD) var extraBindingFlag = _parsingConfig.PrioritizePropertyOrFieldOverTheType && staticAccess ? BindingFlags.Static : BindingFlags.Instance; var bindingFlags = BindingFlags.Public | BindingFlags.DeclaredOnly | extraBindingFlag; foreach (Type t in TypeHelper.GetSelfAndBaseTypes(type)) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs index b173cc23..edcd48b5 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs @@ -55,7 +55,7 @@ public ExpressionPromoter(ParsingConfig config) Type target = TypeHelper.GetNonNullableType(type); object? value = null; -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(UAP10_0 || NETSTANDARD) switch (Type.GetTypeCode(ce.Type)) { case TypeCode.Int32: diff --git a/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs b/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs index 0059fde6..7fdfdaad 100644 --- a/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs @@ -216,7 +216,7 @@ public bool TryParseNumber(string text, Type type, out object? result) { try { -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(UAP10_0 || NETSTANDARD) switch (Type.GetTypeCode(TypeHelper.GetNonNullableType(type))) { case TypeCode.SByte: diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs index 58437110..15dc151d 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs @@ -70,7 +70,7 @@ public bool ContainsMethod(Type type, string methodName, bool staticAccess = tru { Check.NotNull(type); -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(UAP10_0 || NETSTANDARD) var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance); return type.FindMembers(MemberTypes.Method, flags, Type.FilterNameIgnoreCase, methodName).Any(); #else @@ -90,7 +90,7 @@ public bool ContainsMethod(Type type, string methodName, bool staticAccess, Expr public int FindMethod(Type? type, string methodName, bool staticAccess, ref Expression? instance, ref Expression[] args, out MethodBase? method) { -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(UAP10_0 || NETSTANDARD) BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance); foreach (Type t in SelfAndBaseTypes(type)) { @@ -216,7 +216,7 @@ public int FindIndexer(Type type, Expression[] args, out MethodBase? method) if (members.Length != 0) { IEnumerable methods = members.OfType(). -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(UAP10_0 || NETSTANDARD) Select(p => (MethodBase?)p.GetGetMethod()). OfType(); #else diff --git a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs index d164c4b4..526b5c4b 100644 --- a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs @@ -50,7 +50,7 @@ public static bool TryFindGenericType(Type generic, Type? type, [NotNullWhen(tru public static bool IsCompatibleWith(Type source, Type target) { -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(UAP10_0 || NETSTANDARD) if (source == target) { return true; @@ -371,7 +371,7 @@ private static int GetNumericTypeKind(Type type) { type = GetNonNullableType(type); -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(UAP10_0 || NETSTANDARD) if (type.GetTypeInfo().IsEnum) { return 0; diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index 3c80b7bb..cd58ef1a 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -52,7 +52,7 @@ public IDynamicLinkCustomTypeProvider? CustomTypeProvider { get { -#if !(WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(UAP10_0 || NETSTANDARD) // Only use DefaultDynamicLinqCustomTypeProvider for full .NET Framework and .NET Core App 2.x and higher. return _customTypeProvider ??= new DefaultDynamicLinqCustomTypeProvider(this); #else diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs index 73b71b98..ab8bd289 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs @@ -42,7 +42,7 @@ public void DynamicClass_Equals() } [Fact] - public void DynamicClass_OperatorEquality() + public void DynamicClass_OperatorEqualityAndNotEquality() { // Arrange var props = new[] @@ -66,6 +66,12 @@ public void DynamicClass_OperatorEquality() bool equal2 = dynamicInstance2 == dynamicInstance1; equal2.Should().BeTrue(); + + bool notEqual1 = dynamicInstance1 != dynamicInstance2; + notEqual1.Should().BeFalse(); + + bool notEqual2 = dynamicInstance2 != dynamicInstance1; + notEqual2.Should().BeFalse(); } [Fact]