Home > Software design >  Filter a list with a custom FUNC
Filter a list with a custom FUNC

Time:06-25

In my WPF application I have a observable collection and i have to filter passing a func in the where condition. I've decided to passing a Func because the filter condition in where is created dynamically (passing more than a parameter that is in my collection, filter by and/or, filter by less than, equal, etc etc).

That's solution works fine until I've restructured my code. In previous version of application I've had a Model with all property of collection in a unique class. And the expressions where created only by property name. But now I've introduced base classes and interfaces, and something goes wrong. I can't used the property name to create expression but because the property speed is a nested class I've introduced the CreateExpression method and there I think that the property type (double in my example) it's not correctly mappen in my nested class.

In my class for example I've a speed property (double value) and I want to filter my speed property in my collection.

Here a code sample that explain my problem

public interface ICommonData
    {
        DynamicData DynamicData { get; set; }
        string Name { get; set; }
    }
  public class DynamicData
    {
        public double Speed { get; set; }
    }
    public class Data : ICommonData
    {
        public DynamicData DynamicData { get; set; }
        public string Name { get ; set; }
    }
   public partial class MainWindow : Window
    {
        List<Data> datas = new List<Data>();
        public MainWindow()
        {
            InitializeComponent();

            for (int i = 0; i < 10; i  )
            {
                datas.Add(new Data
                {
                    DynamicData = new DynamicData { Speed = i},
                    Name = $"A{i}"
                });
            }
           
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            CreateFilter("2");
        }

        private void CreateFilter(string v)
        {
            var expression = CreateFilterExpression<ICommonData>(v).Compile();

            datas.Where(expression);
        }

        public static Expression<Func<TInput, bool>> CreateFilterExpression<TInput>(string v)
        {
            Expression compareExpression = null;
            ParameterExpression param = Expression.Parameter(typeof(TInput), "");
            Expression lambdaBody = null;

            var expression = CreateExpression(typeof(ICommonData), "DynamicData.Speed");

            compareExpression = Expression.LessThan(expression, Expression.Constant(2D));

            lambdaBody = compareExpression;

            return Expression.Lambda<Func<TInput, bool>>(lambdaBody, param);

        }

        static LambdaExpression CreateExpression(Type type, string propertyName)
        {
            var param = Expression.Parameter(type, "x");
            Expression body = param;
            foreach (var member in propertyName.Split('.'))
            {
                body = Expression.PropertyOrField(body, member);
            }
            return Expression.Lambda(body, param);
        }
    }

And this is the error

System.InvalidOperationException: 'The binary operator LessThan is not defined for the types 'System.Func`2[FilterTest.ICommonData,System.Double]' and 'System.Double'.'

Thanks all for help

CodePudding user response:

When creating Expression trees in code, I recommend comments (and I rarely use comments) to show what is being built.

public static Expression<Func<TInput, bool>> CreateFilterExpression<TInput>(string v) {
    // (TInput p)
    var param = Expression.Parameter(typeof(TInput), "p");
    // p.DynamicData.Speed
    var memberExp = ExpressionForPath(param, "DynamicData.Speed");
    // p.DynamicData.Speed < 2D
    var body = Expression.LessThan(memberExp, Expression.Constant(2D));

    // (TInput p) => p.DynamicData.Speed < 2D
    return Expression.Lambda<Func<TInput, bool>>(body, param);
}

static Expression ExpressionForPath(ParameterExpression param, string memberPath) =>
    memberPath.Split('.').Aggregate((Expression)param, (exp, n) => Expression.PropertyOrField(exp, n));
  • Related