diff --git a/src/DomainCommonExtensions/ArraysExtensions/DynamicListExtensions.cs b/src/DomainCommonExtensions/ArraysExtensions/DynamicListExtensions.cs index bbc6626..5b7983e 100644 --- a/src/DomainCommonExtensions/ArraysExtensions/DynamicListExtensions.cs +++ b/src/DomainCommonExtensions/ArraysExtensions/DynamicListExtensions.cs @@ -18,9 +18,14 @@ using System.Collections.Generic; using System.Linq; -using System.Linq.Dynamic.Core; using System.Linq.Expressions; using DomainCommonExtensions.CommonExtensions; +using DomainCommonExtensions.Helpers.Internal.AnonymousSelect; + +#region OLD Using System.Linq.Dynamic.Core +//using System.Linq.Dynamic.Core; +//using System.Linq.Dynamic.Core.Parser; +#endregion #endregion @@ -32,8 +37,57 @@ namespace DomainCommonExtensions.ArraysExtensions /// public static class DynamicListExtensions { + #region OLD Using System.Linq.Dynamic.Core + + /* + // Using System.Linq.Dynamic.Core + // // + // // Parse input data (List) to dynamic result (list) + // // + // // List of T input data + // // Required fields + // // + // // + // // + // public static IList ParseListOfTInDynamic(this List input, IEnumerable fields = null) + // { + // var data = input.AsQueryable(); + // var requiredFields = typeof(T).GetSelectedFieldFromEntity(fields); + // + // var result = data.Select("new {" + requiredFields + "}").ToDynamicList(); + // + // return result; + // } + + */ + + /* + // Using System.Linq.Dynamic.Core + // /// + // /// Parse input data (IEnumerable) to dynamic result (list) + // /// + // /// IEnumerable of T input data + // /// Required fields + // /// + // /// + // /// + // public static IList ParseEnumerableOfTInDynamic(this IEnumerable input, + // IEnumerable fields = null) + // { + // var data = input.AsQueryable(); + // var requiredFields = typeof(T).GetSelectedFieldFromEntity(fields); + + // var result = data.Select("new {" + requiredFields + "}").ToDynamicList(); + + // return result; + // } + + */ + + #endregion + /// - /// Parse input data (List) to dynamic result (list) + /// Parse input data (IEnumerable) to dynamic result (list) /// /// List of T input data /// Required fields @@ -44,29 +98,45 @@ public static IList ParseListOfTInDynamic(this List input, IEnume { var data = input.AsQueryable(); var requiredFields = typeof(T).GetSelectedFieldFromEntity(fields); + var parameter = Expression.Parameter(data.ElementType, "x"); + var lambdaExpression = ExpressionHelper.ParseLambda(parameter, data.ElementType, false, requiredFields.Split(',')); + + var selectCall = Expression.Call( + typeof(Queryable), + "Select", + new[] { parameter.Type, lambdaExpression.Body.Type/*source.ElementType*/ }, + data.Expression, + Expression.Quote(lambdaExpression)); - var result = data.Select("new {" + requiredFields + "}").ToDynamicList(); + var result = data.Provider.CreateQuery(selectCall); - return result; + return result.Cast().ToList(); } - /// - /// Parse input data (IEnumerable) to dynamic result (list) + /// Parse input data (List) to dynamic result (list) /// /// IEnumerable of T input data /// Required fields /// /// /// - public static IList ParseEnumerableOfTInDynamic(this IEnumerable input, - IEnumerable fields = null) + public static IList ParseEnumerableOfTInDynamic(this IEnumerable input, IEnumerable fields = null) { var data = input.AsQueryable(); var requiredFields = typeof(T).GetSelectedFieldFromEntity(fields); + var parameter = Expression.Parameter(data.ElementType, "x"); + var lambdaExpression = ExpressionHelper.ParseLambda(parameter, data.ElementType, false, requiredFields.Split(',')); - var result = data.Select("new {" + requiredFields + "}").ToDynamicList(); + var selectCall = Expression.Call( + typeof(Queryable), + "Select", + new[] { parameter.Type, lambdaExpression.Body.Type/*source.ElementType*/ }, + data.Expression, + Expression.Quote(lambdaExpression)); + + var result = data.Provider.CreateQuery(selectCall); - return result; + return result.Cast().ToList(); } /// @@ -79,12 +149,36 @@ public static IList ParseEnumerableOfTInDynamic(this IEnumerable public static IQueryable SelectProperty(this IQueryable source, string prop) { var parameter = Expression.Parameter(source.ElementType, "x"); - var property = prop.Split(',') - .Aggregate((Expression)parameter, Expression.Property); - var selector = Expression.Lambda(property, parameter); + var propEx = Expression.Property(parameter, prop); + var selector = Expression.Lambda(propEx, parameter); + var selectCall = Expression.Call( + typeof(Queryable), + "Select", + new[] { parameter.Type, propEx.Type }, + source.Expression, + Expression.Quote(selector)); + + return source.Provider.CreateQuery(selectCall); + } + + /// + /// Generate select multiple properties + /// + /// Data source IQueryable + /// Properties name + /// + /// + public static IQueryable SelectMultipleProperties(this IQueryable source, params string[] props) + { + var parameter = Expression.Parameter(source.ElementType, "x"); + var lambdaExpression = ExpressionHelper.ParseLambda(parameter, source.ElementType, true, props); + var selectCall = Expression.Call( - typeof(Queryable), "Select", new[] { parameter.Type, property.Type }, - source.Expression, Expression.Quote(selector)); + typeof(Queryable), + "Select", + new[] { parameter.Type, lambdaExpression.Body.Type/*source.ElementType*/ }, + source.Expression, + Expression.Quote(lambdaExpression)); return source.Provider.CreateQuery(selectCall); } diff --git a/src/DomainCommonExtensions/CommonExtensions/ReflectionExtensions.cs b/src/DomainCommonExtensions/CommonExtensions/ReflectionExtensions.cs index 9a7a089..56e146e 100644 --- a/src/DomainCommonExtensions/CommonExtensions/ReflectionExtensions.cs +++ b/src/DomainCommonExtensions/CommonExtensions/ReflectionExtensions.cs @@ -52,7 +52,7 @@ public static void CopyProperties(this object source, object destination) var targetProperty = typeDest.GetProperty(srcProp.Name); if (targetProperty.IsNull()) continue; - if (!targetProperty.CanWrite) + if (!targetProperty!.CanWrite) continue; if (targetProperty.GetSetMethod(true) != null && targetProperty.GetSetMethod(true).IsPrivate) continue; diff --git a/src/DomainCommonExtensions/CommonExtensions/TypeBuilderExtensions.cs b/src/DomainCommonExtensions/CommonExtensions/TypeBuilderExtensions.cs new file mode 100644 index 0000000..38f9565 --- /dev/null +++ b/src/DomainCommonExtensions/CommonExtensions/TypeBuilderExtensions.cs @@ -0,0 +1,106 @@ +// *********************************************************************** +// Assembly : RzR.Shared.Extensions.DomainCommonExtensions +// Author : RzR +// Created On : 2025-01-08 09:27 +// +// Last Modified By : RzR +// Last Modified On : 2025-01-08 09:28 +// *********************************************************************** +// +// Copyright © RzR. All rights reserved. +// +// +// +// +// *********************************************************************** + +#region U S A G E S + +// ReSharper disable RedundantUsingDirective + +using System; +using System.Reflection; +using System.Reflection.Emit; + +#endregion + +namespace DomainCommonExtensions.CommonExtensions +{ + /// ------------------------------------------------------------------------------------------------- + /// + /// A type builder extensions. + /// + /// ================================================================================================= + public static class TypeBuilderExtensions + { + +#if !(NET35 || NET40 || SILVERLIGHT || WPSL || UAP10_0) + + /// ------------------------------------------------------------------------------------------------- + /// + /// Creates a System.Type object for the class. After defining fields and methods + /// on the class, CreateType is called in order to load its Type object. + /// + /// The TypeBuilder to act on. + /// + /// Returns the new System.Type object for this class. + /// + /// ================================================================================================= + public static Type CreateType(this TypeBuilder tb) + { + return tb.CreateTypeInfo().AsType(); + } +#endif + +#if NET35 || NET40 || SILVERLIGHT || WPSL || NETCOREAPP || NETSTANDARD2_1 + + /// ------------------------------------------------------------------------------------------------- + /// + /// A TypeBuilder extension method that define property. + /// + /// The TypeBuilder to act on. + /// The name. + /// The attributes. + /// The calling convention. + /// Type of the return. + /// List of types of the parameters. + /// + /// A PropertyBuilder. + /// + /// ================================================================================================= + public static PropertyBuilder DefineProperty(this TypeBuilder tb, string name, PropertyAttributes attributes, + CallingConventions callingConvention, Type returnType, Type[] parameterTypes) + { + return tb.DefineProperty(name, attributes, returnType, parameterTypes); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// A TypeBuilder extension method that converts a builder to a type. + /// + /// The builder to act on. + /// + /// A Type. + /// + /// ================================================================================================= + public static Type AsType(this TypeBuilder builder) + { + return builder; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// A GenericTypeParameterBuilder extension method that converts a builder to a type. + /// + /// The builder to act on. + /// + /// A Type. + /// + /// ================================================================================================= + public static Type AsType(this GenericTypeParameterBuilder builder) + { + return builder; + } +#endif + } +} \ No newline at end of file diff --git a/src/DomainCommonExtensions/DataTypeExtensions/StringExtensions.cs b/src/DomainCommonExtensions/DataTypeExtensions/StringExtensions.cs index 3af6aa1..3d2e1da 100644 --- a/src/DomainCommonExtensions/DataTypeExtensions/StringExtensions.cs +++ b/src/DomainCommonExtensions/DataTypeExtensions/StringExtensions.cs @@ -1574,5 +1574,20 @@ public static bool IsValidJsonArray(this string source) } #endif + + /// + /// Escape backslash from source string + /// + /// Source string + /// Custom char to be escaped in source. Default value is '|'. + /// + /// Replace by default '\' => '\\', and custom char(string value) from 'x' => '\x' + public static string EscapeBackSlash(this string source, string customCharEscape = "|") + { + source = source.Replace(@"\", @"\\"); + source = source.Replace(customCharEscape, @$"\{customCharEscape}"); + + return source; + } } } \ No newline at end of file diff --git a/src/DomainCommonExtensions/DomainCommonExtensions.csproj b/src/DomainCommonExtensions/DomainCommonExtensions.csproj index f80cdbc..8585f3e 100644 --- a/src/DomainCommonExtensions/DomainCommonExtensions.csproj +++ b/src/DomainCommonExtensions/DomainCommonExtensions.csproj @@ -50,8 +50,8 @@ - - + + @@ -66,6 +66,10 @@ + + + + @@ -74,6 +78,18 @@ + + + 4.2.0 + + + + + + 4.2.0 + + + diff --git a/src/DomainCommonExtensions/Helpers/InsensitiveCaseHashtableHelper.cs b/src/DomainCommonExtensions/Helpers/InsensitiveCaseHashtableHelper.cs index 9539d34..0c2c88f 100644 --- a/src/DomainCommonExtensions/Helpers/InsensitiveCaseHashtableHelper.cs +++ b/src/DomainCommonExtensions/Helpers/InsensitiveCaseHashtableHelper.cs @@ -51,7 +51,7 @@ public ArrayList Collection { var arg = arrayList; if (enumerator.Current.IsNull()) continue; - var dictionaryEntry = (DictionaryEntry)enumerator.Current; + var dictionaryEntry = (DictionaryEntry)enumerator.Current!; arg.Add(dictionaryEntry.Value); } diff --git a/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Base/AnonymousClass.cs b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Base/AnonymousClass.cs new file mode 100644 index 0000000..2500b64 --- /dev/null +++ b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Base/AnonymousClass.cs @@ -0,0 +1,223 @@ +// *********************************************************************** +// Assembly : RzR.Shared.Extensions.DomainCommonExtensions +// Author : RzR +// Created On : 2025-01-08 13:29 +// +// Last Modified By : RzR +// Last Modified On : 2025-01-08 18:05 +// *********************************************************************** +// +// Copyright © RzR. All rights reserved. +// +// +// +// +// *********************************************************************** + +#region U S A G E S + +using System.Collections.Generic; +using System.Dynamic; +using System.Reflection; +using CodeSource; +using DomainCommonExtensions.CommonExtensions; + +#endregion + +namespace DomainCommonExtensions.Helpers.Internal.AnonymousSelect.Base +{ + /// ------------------------------------------------------------------------------------------------- + /// + /// The anonymous class. + /// + /// + /// ================================================================================================= + [CodeSource(SourceUrl = "System.Linq.Dynamic.Core.DynamicClass", Version = 1.0D, Comment = "The current implementation has inspiration from a specified source URL.")] + public abstract class AnonymousClass : DynamicObject + { + /// ------------------------------------------------------------------------------------------------- + /// + /// Dictionary of properties. + /// + /// ================================================================================================= + private Dictionary _propertiesDictionary; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets the properties. + /// + /// + /// The properties. + /// + /// ================================================================================================= + private Dictionary Properties + { + get + { + if (_propertiesDictionary.IsNull()) + { + _propertiesDictionary = new Dictionary(); + + foreach (var pi in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var parameters = pi.GetIndexParameters().Length; + if (parameters > 0) + // The property is an indexer, skip this. + continue; + + _propertiesDictionary.Add(pi.Name, pi.GetValue(this, null)); + } + } + + return _propertiesDictionary; + } + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets or sets the with the specified name. + /// + /// The name. + /// The . + /// Value from the property. + /// ================================================================================================= + public object this[string name] + { + get => Properties.TryGetValue(name, out var result) ? result : null; + + set => Properties[name] = value; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets the dynamic property by name. + /// + /// The type. + /// Name of the property. + /// + /// T. + /// + /// ================================================================================================= + public T GetDynamicPropertyValue(string propertyName) + { + var type = GetType(); + var propInfo = type.GetProperty(propertyName); + + return (T)propInfo?.GetValue(this, null); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets the dynamic property value by name. + /// + /// Name of the property. + /// + /// value. + /// + /// ================================================================================================= + public object GetDynamicPropertyValue(string propertyName) => GetDynamicPropertyValue(propertyName); + + /// ------------------------------------------------------------------------------------------------- + /// + /// Sets the dynamic property value by name. + /// + /// The type. + /// Name of the property. + /// The value. + /// ================================================================================================= + public void SetDynamicPropertyValue(string propertyName, T value) + { + var type = GetType(); + var propInfo = type.GetProperty(propertyName); + + propInfo?.SetValue(this, value, null); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Sets the dynamic property value by name. + /// + /// Name of the property. + /// The value. + /// ================================================================================================= + public void SetDynamicPropertyValue(string propertyName, object value) + { + SetDynamicPropertyValue(propertyName, value); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Returns the enumeration of all dynamic member names. + /// + /// + /// A sequence that contains dynamic member names. + /// + /// ================================================================================================= + public override IEnumerable GetDynamicMemberNames() => Properties.Keys; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Provides the implementation for operations that get member values. Classes derived from + /// the + /// class can override this method to specify + /// dynamic behavior for + /// operations such as getting a value for a property. + /// + /// + /// Provides information about the object that called the dynamic operation. The binder.Name + /// property provides the name of the member on which the dynamic operation is performed. For + /// example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where + /// sampleObject is an instance of the class derived from the + /// class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies + /// whether the member name is case-sensitive. + /// + /// + /// [out] The result of the get operation. For example, if the method is called for a + /// property, you can assign the property value to . + /// + /// + /// true if the operation is successful; otherwise, false. If this method returns false, the + /// run-time binder of the language determines the behavior. (In most cases, a run-time + /// exception is thrown.) + /// + /// ================================================================================================= + public override bool TryGetMember(GetMemberBinder binder, out object result) => Properties.TryGetValue(binder.Name, out result); + + /// ------------------------------------------------------------------------------------------------- + /// + /// Provides the implementation for operations that set member values. Classes derived from + /// the + /// class can override this method to specify + /// dynamic behavior for + /// operations such as setting a value for a property. + /// + /// + /// Provides information about the object that called the dynamic operation. The binder.Name + /// property provides the name of the member to which the value is being assigned. For + /// example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an + /// instance of the class derived from the + /// class, binder.Name returns + /// "SampleProperty". The binder.IgnoreCase + /// property specifies whether the member name is case-sensitive. + /// + /// + /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", + /// where sampleObject is an instance of the class derived from the + /// class, the + /// is "Test". + /// + /// + /// true if the operation is successful; otherwise, false. If this method returns false, the + /// run-time binder of the language determines the behavior. (In most cases, a language- + /// specific run-time exception is thrown.) + /// + /// ================================================================================================= + public override bool TrySetMember(SetMemberBinder binder, object value) + { + var name = binder.Name; + Properties[name] = value; + + return true; + } + } +} \ No newline at end of file diff --git a/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/ExpressionHelper.cs b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/ExpressionHelper.cs new file mode 100644 index 0000000..964a1e4 --- /dev/null +++ b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/ExpressionHelper.cs @@ -0,0 +1,178 @@ +// *********************************************************************** +// Assembly : RzR.Shared.Extensions.DomainCommonExtensions +// Author : RzR +// Created On : 2025-01-08 13:30 +// +// Last Modified By : RzR +// Last Modified On : 2025-01-08 17:55 +// *********************************************************************** +// +// Copyright © RzR. All rights reserved. +// +// +// +// +// *********************************************************************** + +#region U S A G E S + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using CodeSource; +using DomainCommonExtensions.CommonExtensions; +using DomainCommonExtensions.DataTypeExtensions; +using DomainCommonExtensions.Helpers.Internal.AnonymousSelect.Factory; + +#endregion + +namespace DomainCommonExtensions.Helpers.Internal.AnonymousSelect +{ + /// ------------------------------------------------------------------------------------------------- + /// + /// An internal expression helper. + /// + /// ================================================================================================= + [CodeSource(SourceUrl = "https://stackoverflow.com/questions/3740532/how-to-use-expression-to-build-an-anonymous-type", Version = 1.0D, Comment = "Access the source URL from more info.")] + [CodeSource(SourceUrl = "https://stackoverflow.com/questions/606104/how-to-create-linq-expression-tree-to-select-an-anonymous-type", Version = 1.0D, Comment = "Access the source URL from more info.")] + [CodeSource(SourceUrl = "https://stackoverflow.com/questions/57524986/selector-expression-dynamic-on-iqueryable", Version = 1.0D, Comment = "Access the source URL from more info.")] + internal static class ExpressionHelper + { + /// ------------------------------------------------------------------------------------------------- + /// + /// Parse lambda. + /// + /// The parameter expression. + /// Type of the element. + /// True to build lambda base on source type. + /// A variable-length parameters list containing properties. + /// + /// A LambdaExpression. + /// + /// ================================================================================================= + internal static LambdaExpression ParseLambda(ParameterExpression paramExpression, Type elementType, + bool buildLambdaBaseOnSourceType, params string[] props) + { + var propsX = new List(); + var memberAssignmentList = new List(); + var propExpressionList = new List(); + foreach (var prop in props) + { + var propInfo = elementType.GetProperty(prop); + var propExpression = Expression.Property(paramExpression, prop); + + propExpressionList.Add(propExpression); + propsX.Add(propInfo); + memberAssignmentList.Add(Expression.Bind(propInfo!, propExpression)); + } + + if (buildLambdaBaseOnSourceType.IsTrue()) + { + var resultDataType = Expression.New(elementType); + var initExpression = Expression.MemberInit(resultDataType, memberAssignmentList); + var lambda = Expression.Lambda(initExpression, paramExpression); + + return lambda; + } + else + { + var resultType = AnonymousClassFactory.CreateType(propsX); + var resultDataType = BuildCtorParamExpression(resultType, propExpressionList, propsX); + //initExpression = Expression.MemberInit(resultDataType, memberAssignmentList); + var lambda = Expression.Lambda(resultDataType, paramExpression); + + return lambda; + } + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Builds constructor parameter expression. + /// + /// Type of the anonymous. + /// The expressions. + /// The properties. + /// + /// An Expression. + /// + /// ================================================================================================= + private static NewExpression BuildCtorParamExpression(Type anonymousType, IList expressions, + IList properties) + { + var propertyInfos = anonymousType.GetProperties().Where(x => x.Name != "Item").ToArray(); + var propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray(); + var ctor = anonymousType.GetConstructor(propertyTypes); + + if (ctor.IsNull()) return null; + + var constructorParameters = ctor!.GetParameters(); + if (constructorParameters.Length == expressions.Count) + { + var expressionsPromoted = new List(); + for (var i = 0; i < constructorParameters.Length; i++) + { + var bindParametersSequentially = !properties.All(p => constructorParameters + .Any(cp => cp.Name == p.Name && (cp.ParameterType == p.PropertyType || + p.PropertyType == Nullable.GetUnderlyingType(cp.ParameterType)))); + if (bindParametersSequentially) + { + expressionsPromoted.Add(ValidateExpression(expressions[i], propertyTypes[i])); + } + else + { + var propertyType = constructorParameters[i].ParameterType; + var cParameterName = constructorParameters[i].Name; + var propertyAndIndex = properties.Select((p, index) => new { p, index }) + .First(p => p.p.Name == cParameterName && (p.p.PropertyType == propertyType || + p.p.PropertyType == Nullable.GetUnderlyingType(propertyType))); + + expressionsPromoted.Add(ValidateExpression(expressions[propertyAndIndex.index], propertyType)); + } + } + + return Expression.New(ctor, expressionsPromoted, (IEnumerable)propertyInfos); + } + + return null; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Validates the expression. + /// + /// Source expression. + /// Type of the destination. + /// + /// An Expression. + /// + /// ================================================================================================= + private static Expression ValidateExpression(Expression sourceExpression, Type destinationType) + { + Type returnType; + if (sourceExpression is LambdaExpression lambdaExpression) + { +#if !NET35 + returnType = lambdaExpression.ReturnType; +#else + returnType = lambdaExpression.Body.Type; +#endif + } + else + { + returnType = sourceExpression.Type; + } + + if (returnType == destinationType || destinationType.IsGenericParameter) return sourceExpression; + + if ((destinationType == typeof(decimal) || destinationType == typeof(int)) && + sourceExpression.Type.IsEnumType()) + return Expression.Convert( + Expression.Convert(sourceExpression, Enum.GetUnderlyingType(sourceExpression.Type)), + destinationType); + + return sourceExpression; + } + } +} \ No newline at end of file diff --git a/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Factory/AnonymousClassFactory.cs b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Factory/AnonymousClassFactory.cs new file mode 100644 index 0000000..59f8e83 --- /dev/null +++ b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Factory/AnonymousClassFactory.cs @@ -0,0 +1,553 @@ +// *********************************************************************** +// Assembly : RzR.Shared.Extensions.DomainCommonExtensions +// Author : RzR +// Created On : 2025-01-08 13:29 +// +// Last Modified By : RzR +// Last Modified On : 2025-01-08 17:59 +// *********************************************************************** +// +// Copyright © RzR. All rights reserved. +// +// +// +// +// *********************************************************************** + +#region U S A G E S + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using CodeSource; +using DomainCommonExtensions.CommonExtensions; +using DomainCommonExtensions.DataTypeExtensions; +using DomainCommonExtensions.Helpers.Internal.AnonymousSelect.Base; + +// ReSharper disable UseArrayEmptyMethod +// ReSharper disable CommentTypo +// ReSharper disable IdentifierTypo + +#endregion + +namespace DomainCommonExtensions.Helpers.Internal.AnonymousSelect.Factory +{ + /// ------------------------------------------------------------------------------------------------- + /// + /// The anonymous class factory. + /// + /// ================================================================================================= + [CodeSource(SourceUrl = "https://stackoverflow.com/questions/29413942/c-sharp-anonymous-object-with-properties-from-dictionary", Version = 1.0D, Comment = "Access the source URL from more info.")] + [CodeSource(SourceUrl = "https://stackoverflow.com/questions/606104/how-to-create-linq-expression-tree-to-select-an-anonymous-type", Version = 1.0D, Comment = "Access the source URL from more info.")] + internal static class AnonymousClassFactory + { + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the current assembly version. + /// + /// ================================================================================================= + private static readonly string CurrentAssemblyVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) name of the anonymous assembly. + /// + /// ================================================================================================= + private static readonly string AnonymousAssemblyName = $"DomainCommonExtensions.Helpers.Factory.Internal.AnonymousClassFactory, Version={CurrentAssemblyVersion}"; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) name of the anonymous module. + /// + /// ================================================================================================= + private const string AnonymousModuleName = "DomainCommonExtensions.Helpers.Factory.Internal.AnonymousClassFactory"; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the compiler generated attribute builder. + /// + /// ================================================================================================= + private static readonly CustomAttributeBuilder CompilerGeneratedAttributeBuilder = new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes)!, new object[0]); + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the debugger hidden attribute builder. + /// + /// ================================================================================================= + private static readonly CustomAttributeBuilder DebuggerHiddenAttributeBuilder = new CustomAttributeBuilder(typeof(DebuggerHiddenAttribute).GetConstructor(Type.EmptyTypes)!, new object[0]); + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the debugger browsable attribute builder. + /// + /// ================================================================================================= + private static readonly CustomAttributeBuilder DebuggerBrowsableAttributeBuilder = new CustomAttributeBuilder(typeof(DebuggerBrowsableAttribute).GetConstructor(new[] { typeof(DebuggerBrowsableState) })!, new object[] { DebuggerBrowsableState.Never }); + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the object constructor. + /// + /// ================================================================================================= + private static readonly ConstructorInfo ObjectCtor = typeof(object).GetConstructor(Type.EmptyTypes)!; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the string builder constructor. + /// + /// ================================================================================================= + private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes)!; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the equality comparer. + /// + /// ================================================================================================= + private static readonly Type EqualityComparer = typeof(EqualityComparer<>); + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) list of types of the generated. + /// + /// ================================================================================================= + private static readonly ConcurrentDictionary GeneratedTypes = new ConcurrentDictionary(); + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the module builder. + /// + /// ================================================================================================= + private static readonly ModuleBuilder ModuleBuilder; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Zero-based index of the. + /// + /// ================================================================================================= + private static int _index = -1; + +#if UAP10_0 || NETSTANDARD + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the object to string. + /// + /// ================================================================================================= + private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public)!; +#else + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the object to string. + /// + /// ================================================================================================= + private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)!; +#endif + +#if UAP10_0 || NETSTANDARD + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the string builder append string. + /// + /// ================================================================================================= + private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new[] { typeof(string) })!; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the string builder append object. + /// + /// ================================================================================================= + private static readonly MethodInfo StringBuilderAppendObject = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new[] { typeof(object) })!; +#else + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the string builder append string. + /// + /// ================================================================================================= + private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null)!; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the string builder append object. + /// + /// ================================================================================================= + private static readonly MethodInfo StringBuilderAppendObject = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(object) }, null)!; +#endif + + /// ------------------------------------------------------------------------------------------------- + /// + /// Initializes static members of the class. + /// + /// ================================================================================================= + static AnonymousClassFactory() + { + var assemblyName = new AssemblyName(AnonymousAssemblyName); + var assemblyBuilder = AssemblyBuilderFactory.DefineDynamicAssembly + ( + assemblyName, +#if NET35 + AssemblyBuilderAccess.Run +#else + AssemblyBuilderAccess.RunAndCollect +#endif + ); + + ModuleBuilder = assemblyBuilder.DefineDynamicModule(AnonymousModuleName); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Creates a type. + /// + /// The properties. + /// (Optional) True to create parameter constructor. + /// + /// The new type. + /// + /// ================================================================================================= + public static Type CreateType(IList properties, bool createParameterCtor = true) + { + var key = GenerateKey(properties, createParameterCtor); + + // 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) + { + return GeneratedTypes.GetOrAdd(key, _ => EmitType(properties, createParameterCtor)); + } + + return type; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Emit type. + /// + /// The properties. + /// True to create parameter constructor. + /// + /// A Type. + /// + /// ================================================================================================= + [CodeSource(SourceUrl = "https://stackoverflow.com/questions/29413942/c-sharp-anonymous-object-with-properties-from-dictionary", Version = 1.0D)] + 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 typeBuilder = ModuleBuilder.DefineType(typeName, + TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoLayout | + TypeAttributes.BeforeFieldInit, typeof(AnonymousClass)); + typeBuilder.SetCustomAttribute(CompilerGeneratedAttributeBuilder); + + var fieldBuilders = new FieldBuilder[properties.Count]; + + // There are two for-loops because we want to have all the getter methods before all the other methods + for (var i = 0; i < properties.Count; i++) + { + var fieldName = properties[i].Name; + var fieldType = properties[i].PropertyType; + + // field + fieldBuilders[i] = typeBuilder.DefineField($"<{fieldName}>i__Field", fieldType, + FieldAttributes.Private | FieldAttributes.InitOnly); + fieldBuilders[i].SetCustomAttribute(DebuggerBrowsableAttributeBuilder); + + var propertyBuilder = typeBuilder.DefineProperty(fieldName, PropertyAttributes.None, + CallingConventions.HasThis, fieldType, Type.EmptyTypes); + + // getter + var getter = typeBuilder.DefineMethod($"get_{fieldName}", + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, + CallingConventions.HasThis, fieldType, null); + getter.SetCustomAttribute(CompilerGeneratedAttributeBuilder); + + var ilgeneratorGetter = getter.GetILGenerator(); + ilgeneratorGetter.Emit(OpCodes.Ldarg_0); + ilgeneratorGetter.Emit(OpCodes.Ldfld, fieldBuilders[i]); + ilgeneratorGetter.Emit(OpCodes.Ret); + propertyBuilder.SetGetMethod(getter); + + // setter + var setter = typeBuilder.DefineMethod($"set_{fieldName}", + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, + CallingConventions.HasThis, null, new[] { fieldType }); + setter.SetCustomAttribute(CompilerGeneratedAttributeBuilder); + + // workaround for https://github.com/dotnet/corefx/issues/7792 + setter.DefineParameter(1, ParameterAttributes.In, properties[i].Name); + + var 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); + } + + // ToString() + var toString = typeBuilder.DefineMethod(nameof(ToString), + MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, + CallingConventions.HasThis, typeof(string), Type.EmptyTypes); + toString.SetCustomAttribute(DebuggerHiddenAttributeBuilder); + + var ilgeneratorToString = toString.GetILGenerator(); + ilgeneratorToString.DeclareLocal(typeof(StringBuilder)); + ilgeneratorToString.Emit(OpCodes.Newobj, StringBuilderCtor); + ilgeneratorToString.Emit(OpCodes.Stloc_0); + + // Equals() + var equals = typeBuilder.DefineMethod(nameof(Equals), + MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, + CallingConventions.HasThis, typeof(bool), new[] { typeof(object) }); + equals.DefineParameter(1, ParameterAttributes.In, "value"); + equals.SetCustomAttribute(DebuggerHiddenAttributeBuilder); + + var 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); + + // GetHashCode() + var getHashCode = typeBuilder.DefineMethod(nameof(GetHashCode), + MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, + CallingConventions.HasThis, typeof(int), Type.EmptyTypes); + getHashCode.SetCustomAttribute(DebuggerHiddenAttributeBuilder); + + var ilgeneratorGetHashCode = getHashCode.GetILGenerator(); + ilgeneratorGetHashCode.DeclareLocal(typeof(int)); + + 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 + var initHash = 0; + + for (var i = 0; i < properties.Count; i++) + initHash = unchecked(initHash * -1521134295 + fieldBuilders[i].Name.GetHashCode()); + + // Note that the CSC seems to generate a different seed for every anonymous class + ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, initHash); + } + + var equalsLabel = ilgeneratorEquals.DefineLabel(); + + for (var i = 0; i < properties.Count; i++) + { + var fieldName = properties[i].Name; + var fieldType = properties[i].PropertyType; + var equalityComparerT = EqualityComparer.MakeGenericType(fieldType); + + // Equals() + var equalityComparerTDefault = + equalityComparerT.GetMethod("get_Default", BindingFlags.Static | BindingFlags.Public)!; + var equalityComparerTEquals = equalityComparerT.GetMethod(nameof(EqualityComparer.Equals), + BindingFlags.Instance | BindingFlags.Public, null, new[] { 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(); + var equalityComparerTGetHashCode = equalityComparerT.GetMethod(nameof(EqualityComparer.GetHashCode), + BindingFlags.Instance | BindingFlags.Public, null, new[] { 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].PropertyType); + ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendObject); + ilgeneratorToString.Emit(OpCodes.Pop); + } + + // 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()) + { + // .ctor default + var constructorDef = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, + CallingConventions.HasThis, Type.EmptyTypes); + constructorDef.SetCustomAttribute(DebuggerHiddenAttributeBuilder); + + var 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.PropertyType).ToArray(); + var constructor = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, + CallingConventions.HasThis, types); + constructor.SetCustomAttribute(DebuggerHiddenAttributeBuilder); + + var ilgeneratorConstructor = constructor.GetILGenerator(); + ilgeneratorConstructor.Emit(OpCodes.Ldarg_0); + ilgeneratorConstructor.Emit(OpCodes.Call, ObjectCtor); + + 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]); + } + + ilgeneratorConstructor.Emit(OpCodes.Ret); + } + + // 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); + + // 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(); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Emit equality operators. + /// + /// The type builder. + /// The equals. + /// ================================================================================================= + private static void EmitEqualityOperators(TypeBuilder typeBuilder, MethodBuilder equals) + { + // Define the '==' operator + var equalityOperator = typeBuilder.DefineMethod( + "op_Equality", + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.SpecialName | + MethodAttributes.HideBySig, + typeof(bool), + new[] { typeBuilder.AsType(), typeBuilder.AsType() }); + + var ilgeneratorEqualityOperator = equalityOperator.GetILGenerator(); + + // if (left == null || right == null) return ReferenceEquals(left, right); + var 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 + var inequalityOperator = typeBuilder.DefineMethod( + "op_Inequality", + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.SpecialName | + MethodAttributes.HideBySig, + typeof(bool), + new[] { typeBuilder.AsType(), typeBuilder.AsType() }); + + var 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 a key. + /// + /// The dynamic properties. + /// True to create parameter constructor. + /// + /// The key. + /// + /// ================================================================================================= + private static string GenerateKey(IEnumerable dynamicProperties, bool createParameterCtor) + => $"{string.Join("|", dynamicProperties.Select(p => p.Name.EscapeBackSlash() + "~" + p.PropertyType).ToArray())}_{(createParameterCtor ? "c" : string.Empty)}"; + } +} \ No newline at end of file diff --git a/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Factory/AssemblyBuilderFactory.cs b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Factory/AssemblyBuilderFactory.cs new file mode 100644 index 0000000..9586572 --- /dev/null +++ b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Factory/AssemblyBuilderFactory.cs @@ -0,0 +1,59 @@ +// *********************************************************************** +// Assembly : RzR.Shared.Extensions.DomainCommonExtensions +// Author : RzR +// Created On : 2025-01-08 09:30 +// +// Last Modified By : RzR +// Last Modified On : 2025-01-08 09:32 +// *********************************************************************** +// +// Copyright © RzR. All rights reserved. +// +// +// +// +// *********************************************************************** + +#if !UAP10_0 + +#region U S A G E S + +#if NET40 +using System; +#endif + +using System.Reflection; +using System.Reflection.Emit; + +#endregion + +namespace DomainCommonExtensions.Helpers.Internal.AnonymousSelect.Factory +{ + /// ------------------------------------------------------------------------------------------------- + /// + /// An assembly builder factory. + /// + /// ================================================================================================= + internal static class AssemblyBuilderFactory + { + /// ------------------------------------------------------------------------------------------------- + /// + /// Defines a dynamic assembly that has the specified name and access rights. + /// + /// The name of the assembly. + /// The access rights of the assembly. + /// + /// An object that represents the new assembly. + /// + /// ================================================================================================= + internal static AssemblyBuilder DefineDynamicAssembly(AssemblyName name, AssemblyBuilderAccess access) + { +#if (NET35 || NET40 || SILVERLIGHT) + return AppDomain.CurrentDomain.DefineDynamicAssembly(name, access); +#else + return AssemblyBuilder.DefineDynamicAssembly(name, access); +#endif + } + } +} +#endif \ No newline at end of file diff --git a/src/tests/DataTypeTests/DataTypeTests.csproj b/src/tests/DataTypeTests/DataTypeTests.csproj index d552f06..5fec6aa 100644 --- a/src/tests/DataTypeTests/DataTypeTests.csproj +++ b/src/tests/DataTypeTests/DataTypeTests.csproj @@ -25,10 +25,6 @@ - - - - diff --git a/src/tests/DataTypeTests/DynamicListTests.cs b/src/tests/DataTypeTests/DynamicListTests.cs new file mode 100644 index 0000000..2183a5f --- /dev/null +++ b/src/tests/DataTypeTests/DynamicListTests.cs @@ -0,0 +1,289 @@ +// *********************************************************************** +// Assembly : RzR.Shared.Extensions.DataTypeTests +// Author : RzR +// Created On : 2025-01-03 19:33 +// +// Last Modified By : RzR +// Last Modified On : 2025-01-03 19:33 +// *********************************************************************** +// +// Copyright © RzR. All rights reserved. +// +// +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using DataTypeTests.Models; +using DomainCommonExtensions.ArraysExtensions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +// ReSharper disable ArrangeObjectCreationWhenTypeEvident + +namespace DataTypeTests +{ + [TestClass] + public class DynamicListTests + { + private ICollection _sourceData; + + [TestInitialize] + public void Init() + => _sourceData = new List() + { + new TempModel() + { + Id = 0, + Code = "Code-000", + Name = "Name-000", + IsActive = true, + DeletedAt = null, + CreatedAt = DateTime.Now.AddDays(-10), + Version = 1, + Price = null, + RecordType = RecordType.Undefined, + DblValue1 = 22.22, + DblValue2 = 22.22 + }, + new TempModel() + { + Id = 1, + Code = "Code-001", + Name = "Name-001", + IsActive = false, + DeletedAt = DateTime.Now.Date, + CreatedAt = DateTime.Now.AddDays(-20), + Version = (decimal)1.5, + Price = 10, + RecordType = RecordType.Primary, + DblValue1 = 0, + DblValue2 = 0 + }, + new TempModel() + { + Id = 2, + Code = "Code-002", + Name = "Name-002", + IsActive = true, + DeletedAt = null, + CreatedAt = DateTime.Now.AddDays(-30), + Version = (decimal)2.8, + Price = (decimal)10.55, + RecordType = RecordType.Secondary, + DblValue1 = 10.99, + DblValue2 = 10.99 + }, + new TempModel() + { + Id = 3, + Code = "Code-003", + Name = "Name-003", + IsActive = false, + DeletedAt = DateTime.Now.Date, + CreatedAt = DateTime.Now.AddDays(-40), + Version = 9, + Price = (decimal)200.99, + DblValue1 = 99.99, + DblValue2 = null + } + }; + + #region OLD Using System.Linq.Dynamic.Core + + /* + // Using System.Linq.Dynamic.Core + [TestMethod] + public void ParseListOfTInDynamicTest() + { + var temp = _sourceData.ToList(); + + var selected = temp.ParseListOfTInDynamic(new[] + { + nameof(TempModel.Id), + nameof(TempModel.Code) + }); + + Assert.IsNotNull(selected); + Assert.AreEqual(_sourceData.Count, selected.Count); + Assert.AreEqual(_sourceData.First().Id, selected.First().Id); + Assert.AreEqual(_sourceData.First().Code, selected.First().Code); + } + */ + + /* + // Using System.Linq.Dynamic.Core + [TestMethod] + public void ParseEnumerableOfTInDynamicTest() + { + var temp = (IEnumerable)_sourceData; + + var selected = temp.ParseEnumerableOfTInDynamic(new[] + { + nameof(TempModel.Id), + nameof(TempModel.Code) + }); + + Assert.IsNotNull(selected); + Assert.AreEqual(_sourceData.Count, selected.Count); + Assert.AreEqual(_sourceData.First().Id, selected.First().Id); + Assert.AreEqual(_sourceData.First().Code, selected.First().Code); + } + */ + + #endregion + + [TestMethod] + public void SelectPropertyTest() + { + var temp = _sourceData.AsQueryable(); + + // .Select(x => x.Id) + var selected = temp.SelectProperty(nameof(TempModel.Id)); + + Assert.IsNotNull(selected); + + var items = selected.Cast().ToList(); + + Assert.IsNotNull(items); + Assert.AreEqual(_sourceData.Count, items.Count); + Assert.AreEqual(_sourceData.First().Id, items.First()); + } + + [TestMethod] + public void SelectMultiplePropertyTest() + { + var temp = _sourceData.AsQueryable(); + + // .Select(x => new {x.Id, x.Code}) + var selected = temp.SelectMultipleProperties(nameof(TempModel.Id), nameof(TempModel.Code)); + + Assert.IsNotNull(selected); + + var items = selected.Cast().ToList(); + + Assert.IsNotNull(items); + Assert.AreEqual(_sourceData.Count, items.Count); + Assert.AreEqual(_sourceData.First().Id, ((TempModel)items.First()).Id); + } + + [TestMethod] + public void ParseListOfTInDynamic_Local_Test() + { + var temp = _sourceData.ToList(); + + var selected = temp.ParseListOfTInDynamic(new[] + { + nameof(TempModel.Id), + nameof(TempModel.Code) + }); + + Assert.IsNotNull(selected); + Assert.AreEqual(_sourceData.Count, selected.Count); + + var sourceFirst = _sourceData.First(); + var selectFirst = selected.First(); + + Assert.IsNotNull(sourceFirst); + Assert.IsNotNull(selectFirst); + Assert.AreEqual(sourceFirst.Id, selectFirst.Id); + Assert.AreEqual(sourceFirst.Code, selectFirst.Code); + } + + [TestMethod] + public void ParseEnumerableOfTInDynamic_Local_Test() + { + var temp = _sourceData.ToList(); + + var selected = temp.ParseEnumerableOfTInDynamic(new[] + { + nameof(TempModel.Id), + nameof(TempModel.Code) + }); + + Assert.IsNotNull(selected); + Assert.AreEqual(_sourceData.Count, selected.Count); + + var sourceFirst = _sourceData.First(); + var selectFirst = selected.First(); + + Assert.IsNotNull(sourceFirst); + Assert.IsNotNull(selectFirst); + Assert.AreEqual(sourceFirst.Id, selectFirst.Id); + Assert.AreEqual(sourceFirst.Code, selectFirst.Code); + } + + [TestMethod] + public void ParseEnumerableOfTInDynamic_Local_SelectNullableProp_Test() + { + var temp = _sourceData.ToList(); + + var selected = temp.ParseEnumerableOfTInDynamic(new[] + { + nameof(TempModel.Id), + nameof(TempModel.Code), + nameof(TempModel.CreatedAt), + nameof(TempModel.DeletedAt) + }); + + Assert.IsNotNull(selected); + Assert.AreEqual(_sourceData.Count, selected.Count); + + var sourceFirst = _sourceData.First(); + var selectFirst = selected.First(); + + Assert.IsNotNull(sourceFirst); + Assert.IsNotNull(selectFirst); + Assert.AreEqual(sourceFirst.Id, selectFirst.Id); + Assert.AreEqual(sourceFirst.Code, selectFirst.Code); + Assert.AreEqual(sourceFirst.DeletedAt, selectFirst.DeletedAt); + } + + [TestMethod] + public void ParseEnumerableOfTInDynamic_Local_SelectEnumProp_Test() + { + var temp = _sourceData.ToList(); + + var selected = temp.ParseEnumerableOfTInDynamic(new[] + { + nameof(TempModel.Id), + nameof(TempModel.Code), + nameof(TempModel.RecordType) + }); + + Assert.IsNotNull(selected); + Assert.AreEqual(_sourceData.Count, selected.Count); + + var sourceFirst = _sourceData.First(); + var selectFirst = selected.First(); + + Assert.IsNotNull(sourceFirst); + Assert.IsNotNull(selectFirst); + Assert.AreEqual(sourceFirst.Id, selectFirst.Id); + Assert.AreEqual(sourceFirst.Code, selectFirst.Code); + Assert.AreEqual(sourceFirst.RecordType, selectFirst.RecordType); + } + + [TestMethod] + public void ParseEnumerableOfTInDynamic_Local_SelectDoubleProp_Test() + { + var temp = _sourceData.ToList(); + + var selected = temp.ParseEnumerableOfTInDynamic(new[] + { + nameof(TempModel.DblValue2) + }); + + Assert.IsNotNull(selected); + Assert.AreEqual(_sourceData.Count, selected.Count); + + var sourceFirst = _sourceData.First(); + var selectFirst = selected.First(); + + Assert.IsNotNull(sourceFirst); + Assert.IsNotNull(selectFirst); + Assert.AreEqual(sourceFirst.DblValue2, selectFirst.DblValue2); + } + } +} \ No newline at end of file diff --git a/src/tests/DataTypeTests/Models/RecordType.cs b/src/tests/DataTypeTests/Models/RecordType.cs new file mode 100644 index 0000000..f34657a --- /dev/null +++ b/src/tests/DataTypeTests/Models/RecordType.cs @@ -0,0 +1,25 @@ +// *********************************************************************** +// Assembly : RzR.Shared.Extensions.DataTypeTests +// Author : RzR +// Created On : 2025-01-08 17:03 +// +// Last Modified By : RzR +// Last Modified On : 2025-01-08 17:03 +// *********************************************************************** +// +// Copyright © RzR. All rights reserved. +// +// +// +// +// *********************************************************************** + +namespace DataTypeTests.Models +{ + public enum RecordType + { + Undefined, + Primary, + Secondary + } +} \ No newline at end of file diff --git a/src/tests/DataTypeTests/Models/TempModel.cs b/src/tests/DataTypeTests/Models/TempModel.cs index 083e287..c67c301 100644 --- a/src/tests/DataTypeTests/Models/TempModel.cs +++ b/src/tests/DataTypeTests/Models/TempModel.cs @@ -14,12 +14,32 @@ // // *********************************************************************** +using System; + namespace DataTypeTests.Models { public class TempModel { public int Id { get; set; } + public string Name { get; set; } + public string Code { get; set; } + + public DateTime CreatedAt { get; set; } + + public DateTime? DeletedAt { get; set; } + + public decimal Version { get; set; } + + public decimal? Price { get; set; } + + public RecordType RecordType { get; set; } + + public bool IsActive { get; set; } + + public double DblValue1 { get; set; } + + public double? DblValue2 { get; set; } } } \ No newline at end of file