using System.Linq.Expressions; using System.Reflection; namespace Modules.Library.Domain.EntityBuilders; public abstract class Builder { public abstract T Build(); protected static Func CreateInstanceFunction() { //var ex = Expression.MemberInit(Expression.New(typeof(T))); var type = typeof(T); var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, []) ?? throw new Exception($"Private parameterless constructor is not found for type [{type.Name}]"); var ex = Expression.New(ctor); var lambda = Expression.Lambda>(ex); return lambda.Compile(); } protected static Action SetInstanceFieldsAction() { var entityType = typeof(T); var builderType = typeof(TBuilder); var builder = Expression.Parameter(builderType, "builder"); var entity = Expression.Parameter(entityType, "entity"); var expressions = new List(); var fields = builderType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); foreach (var prop in entityType.GetProperties()) { var field = builderType.GetField(BuilderFieldConventionName(prop.Name), BindingFlags.NonPublic | BindingFlags.Instance); if (field != null) { var builderField = Expression.Field(builder, field); Expression> fieldGetValueLambda = (obj, fieldInfo) => fieldInfo.GetValue(obj); var fieldGetValueExpr = Expression.Invoke(fieldGetValueLambda, [builder, Expression.Constant(field)]); if (IsReadonlyCollection(prop.PropertyType)) { var entityField = entityType.GetField(field.Name, BindingFlags.Instance | BindingFlags.NonPublic); if (entityField != null) expressions.Add(Expression.Assign(Expression.Field(entity, entityField), builderField)); } else { //var field = fields.FirstOrDefault(q => q.Name == BuilderFieldConventionName(prop.Name)); var entityProp = Expression.Property(entity, prop); Expression> propertySetValueExpr = (dst, property, value) => property.SetValue(dst, value); if (IsNullable(builderField) && !IsNullable(entityProp)) { var valueProperty = typeof(Nullable<>).GetProperty("Value")!; Expression> fieldGetValueFromNullableLambda = (obj, fieldInfo) => valueProperty.GetValue(fieldInfo.GetValue(obj))!; var fieldGetValueFromNullableExpr = Expression.Invoke(fieldGetValueFromNullableLambda, [builder, Expression.Constant(field)]); var setFromNullableExpr = Expression.Invoke(propertySetValueExpr, [entity, Expression.Constant(prop), fieldGetValueFromNullableExpr]); expressions.Add(Expression.IfThen(Expression.IsTrue(IsNullExpression(builderField)), setFromNullableExpr)); } else { expressions.Add(Expression.Invoke(propertySetValueExpr, [entity, Expression.Constant(prop), fieldGetValueExpr])); } //expressions.Add(cond); } } } expressions.Add(entity); var lambda = Expression.Lambda>(Expression.Block(expressions), builder, entity); return lambda.Compile(); } private static Expression GetValue(MemberExpression member) => Expression.Convert(member, typeof(object)); private static Expression GetValueFromNullable(Expression objExpression, MemberExpression member) { var valueProperty = typeof(Nullable<>).GetProperty("Value")!; var methodInfo = valueProperty.GetType().GetMethod("GetValue") ?? throw new Exception("Method \"GetValue\" is not found for nullable property"); return Expression.Call(objExpression, methodInfo, GetValue(member)); } private static Expression IsNullExpression(MemberExpression member) { var info = member.Member; return Expression.Equal(Expression.Convert(member, typeof(Nullable<>).MakeGenericType(MemberType(info))), Expression.Constant(null)); } private static bool IsNullable(MemberExpression member) => MemberType(member.Member) == typeof(Nullable<>); private static Type MemberType(MemberInfo info) => info.MemberType switch { MemberTypes.Event => ((EventInfo)info).EventHandlerType!, MemberTypes.Field => ((FieldInfo)info).FieldType, MemberTypes.Method => ((MethodInfo)info).ReturnType, MemberTypes.Property => ((PropertyInfo)info).PropertyType, _ => throw new NotImplementedException() }; private static bool IsReadonlyCollection(Type collectionType) => collectionType.IsGenericType && collectionType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>); private static string BuilderFieldConventionName(string propertyName) => string.Concat('_', char.ToLower(propertyName[0]), propertyName[1..]); }