I have the helper class which translate list TreeField structures to some generic type, and I have a problem with initializing the fields. More precisely, I want to put the logic for each type into separate handlers so that I can add them without changing the logic of ExpressionHelper itself.
What exactly i wanna separate.
var propertyType = property.PropertyType;
if (propertyType.IsPrimitive || propertyType == typeof(string))
{
var memberExpression = Expression.Property(parameter, property.Name);
var bind = Expression.Bind(property, memberExpression);
bindings.Add(bind);
}
else if (type.IsClass)
{
var memberExpression = Expression.Property(parameter, property.Name);
var subFieldsInit = GetMemberInitExpression(property.PropertyType, memberExpression, field.Children);
var bind = Expression.Bind(property, subFieldsInit);
bindings.Add(bind);
}
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
var elementType = property.PropertyType
.GetGenericArguments()
.FirstOrDefault() ?? throw new InvalidOperationException($"Generic type for collection {property.Name} not founded.");
var selectMethod = typeof(Enumerable).GetMethods()
.Where(m => m.Name == nameof(Enumerable.Select))
.First(m => m.GetParameters().Length == 2)
.MakeGenericMethod(elementType, elementType);
var lambdaParameter = Expression.Parameter(elementType);
var lambdaBody = GetMemberInitExpression(elementType, lambdaParameter, field.Children);
var selectLambda = Expression.Lambda(lambdaBody, lambdaParameter);
var memberAccess = Expression.Property(parameter, property);
var call = Expression.Call(selectMethod, memberAccess, selectLambda);
var bind = Expression.Bind(property, call);
bindings.Add(bind);
}
Is there any pattern that fits my situation or should I structure the code differently? All other code.
public readonly struct TreeField(string name, IEnumerable<TreeField> children)
{
public required string Name { get; init; } = name ?? string.Empty;
public IEnumerable<TreeField> Children { get; init; } = children ?? Enumerable.Empty<TreeField>();
}
public sealed class ExpressionHelper
{
public Expression<Func<TEntity, TEntity>> GetLambdaExpression<TEntity>(IEnumerable<TreeField> fields)
{
var parameter = Expression.Parameter(typeof(TEntity));
var result = GetMemberInitExpression(typeof(TEntity), parameter, fields);
return Expression.Lambda<Func<TEntity, TEntity>>(result, parameter);
}
private MemberInitExpression GetMemberInitExpression(Type type, Expression parameter, IEnumerable<TreeField> fields)
{
var bindings = new List<MemberBinding>();
foreach (var field in fields)
{
var property = type.GetProperty(field.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (property is not null)
{
var propertyType = property.PropertyType;
if (propertyType.IsPrimitive || propertyType == typeof(string))
{
var memberExpression = Expression.Property(parameter, property.Name);
var bind = Expression.Bind(property, memberExpression);
bindings.Add(bind);
}
else if (type.IsClass)
{
var memberExpression = Expression.Property(parameter, property.Name);
var subFieldsInit = GetMemberInitExpression(property.PropertyType, memberExpression, field.Children);
var bind = Expression.Bind(property, subFieldsInit);
bindings.Add(bind);
}
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
var elementType = property.PropertyType
.GetGenericArguments()
.FirstOrDefault() ?? throw new InvalidOperationException($"Generic type for collection {property.Name} not founded.");
var selectMethod = typeof(Enumerable).GetMethods()
.Where(m => m.Name == nameof(Enumerable.Select))
.First(m => m.GetParameters().Length == 2)
.MakeGenericMethod(elementType, elementType);
var lambdaParameter = Expression.Parameter(elementType);
var lambdaBody = GetMemberInitExpression(elementType, lambdaParameter, field.Children);
var selectLambda = Expression.Lambda(lambdaBody, lambdaParameter);
var memberAccess = Expression.Property(parameter, property);
var call = Expression.Call(selectMethod, memberAccess, selectLambda);
var bind = Expression.Bind(property, call);
bindings.Add(bind);
}
}
}
return Expression.MemberInit(Expression.New(type), bindings);
}
}
I tried to do this with something like a strategy pattern. But I had a problem with passing
Func<Type, Expression, IEnumerable<TreeField>, MemberInitExpression> memberInit
into the strategy.
public sealed class BindingContext : IBindingContext
{
private readonly IEnumerable<IBindingStrategy> strategies;
public BindingContext(IEnumerable<IBindingStrategy> strategies)
{
this.strategies = strategies;
}
public MemberBinding Bind(PropertyInfo property, Expression parameter, TreeField field, Func<Type, Expression, IEnumerable<TreeField>, MemberInitExpression> memberInit)
{
var strategy = strategies.FirstOrDefault(s => s.AppliesTo(property.PropertyType))
?? throw new NullReferenceException($"Binding startegy for {property.PropertyType} not registered.");
return strategy.Bind(property, parameter, field, memberInit);
}
}
public sealed class EntityStrategy : IBindingStrategy
{
public bool AppliesTo(Type type) => type.IsClass && type != typeof(string);
public MemberBinding Bind(PropertyInfo property, Expression parameter, TreeField field, Func<Type, Expression, IEnumerable<TreeField>, MemberInitExpression> memberInit)
{
var memberExpression = Expression.Property(parameter, property.Name);
var subFieldsInit = memberInit(property.PropertyType, memberExpression, field.Children);
return Expression.Bind(property, subFieldsInit);
}
}
Aucun commentaire:
Enregistrer un commentaire