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));