将 method/properties 链转换为表达式树
Convert method/properties chain into Expression Tree
我有这个类:
public class DiagnosticDetails
{
public string PrimaryValue { get; set; }
public Guid? Id { get; set; }
public IEnumerable<DiagnosticValues> Values { get; set; }
}
public class DiagnosticValues
{
public string Type { get; set; }
public string Value { get; set; }
}
我想为 Values
中的每个字符串构造动态 OrderBy().ThenBy()...
(此列表可以包含不同数量的字符串)因此它应该如下所示:
diagnosticDetails
.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim())
.ThenBy(detail => detail.Values.ElementAt(1).Value?.Trim())
.ThenBy(detail => detail.Values.ElementAt(2).Value?.Trim())
...
但我无法将其转换为表达式树:
var result = diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim());
//var expressionTreeForResult = ???
这是我的代码:
private List<DiagnosticDetails> SortByValue(List<DiagnosticDetails> diagnosticDetails)
{
IQueryable<DiagnosticDetails> queryableData = diagnosticDetails.AsQueryable();
ParameterExpression pe = Expression.Parameter(typeof(DiagnosticDetails), "detail");
//var predicate = diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim());
//var predicateExpressionTree = ???
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<DiagnosticDetails, string>>(predicateExpressionTree, new ParameterExpression[] { pe }));
var maxElementsInValues = diagnosticDetails.Select(dd => dd.Values.Count()).Max();
for (int i = 1; i < maxElementsInValues; i++)
{
orderByCallExpression = Expression.Call(
typeof(Queryable),
"ThenBy",
new Type[] { queryableData.ElementType },
orderByCallExpression,
Expression.Lambda<Func<DiagnosticDetails, string>>(predicateExpressionTree, new ParameterExpression[] { pe }));
}
var sortedDiagnosticDetails = Expression.Lambda<List<DiagnosticDetails>>(orderByCallExpression).Compile();
return sortedDiagnosticDetails;
}
如何将.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim())
转化为表达式树?
正如 Ivan Stoev 在评论中提到的,在这种特殊情况下无需创建表达式树。
实施比较方法会容易得多:
public static int CompareDiagnosticDetailsByValuesConsistently(DiagnosticDetails dd1, DiagnosticDetails dd2)
{
var maxDimension = Math.Max(dd1.Values.Count(), dd2.Values.Count());
for (int i = 0; i < maxDimension; i++)
{
if (dd1.Values.ElementAtOrDefault(i)?.Value == null)
{
if (dd2.Values.ElementAtOrDefault(i)?.Value == null)
continue;
return 1;
}
int result = dd1.Values.ElementAt(i).Value.CompareTo(dd2.Values.ElementAt(i).Value);
if (result == 0)
continue;
return result;
}
return 0;
}
并这样使用它:
diagnosticDetails.Sort(CompareDiagnosticDetailsByValuesConsistently);
但如果您仍想使用表达式树,此代码可满足您的需要:
private List<DiagnosticDetails> SortByAllValuesConsistently(List<DiagnosticDetails> diagnosticDetails)
{
/*
diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value)
.ThenBy(detail => detail.Values.ElementAt(1).Value))
.ThenBy(detail => detail.Values.ElementAt(2).Value))
.ThenBy(detail => detail.Values.ElementAt(3).Value))
...
for each value in detail.Values.
*/
if (diagnosticDetails.IsNullOrEmpty())
return diagnosticDetails;
IQueryable<DiagnosticDetails> queryableData = diagnosticDetails.AsQueryable();
// detail.Values
ParameterExpression p = Expression.Parameter(typeof(DiagnosticDetails), "detail");
MemberExpression prVs = Expression.Property(p, "Values");
// detail.Values.ElementAt(0).
ConstantExpression c0 = Expression.Constant(0, typeof(int));
Expression callElAt = expressionTreeHelper.CallElementAt(prVs, c0);
// detail.Values.ElementAt(0).Value
MemberExpression prV = Expression.Property(callElAt, "Value");
// detail => detail.Values.ElementAt(0).Value
Delegate predicate = Expression.Lambda(prV,p).Compile();
// diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value)
Expression orderByCallExpression = expressionTreeHelper.CallOrderBy(queryableData.Expression, predicate);
var maxElementsInValues = diagnosticDetails.Select(dd => dd.Values.Count()).Max();
for (int i = 1; i < maxElementsInValues; i++)
{
// detail.Values
ParameterExpression pi = Expression.Parameter(typeof(DiagnosticDetails), "detail");
MemberExpression prVsi = Expression.Property(pi, "Values");
// detail.Values.ElementAt(i).
ConstantExpression ci = Expression.Constant(i, typeof(int));
Expression callElAti = expressionTreeHelper.CallElementAt(prVsi, ci);
// detail.Values.ElementAt(i).Value
MemberExpression prVi = Expression.Property(callElAti, "Value");
// detail => detail.Values.ElementAt(i).Value
Delegate predicateI = Expression.Lambda(prVi, pi).Compile();
// orderByCallExpression.ThenBy(detail => detail.Values.ElementAt(0).Value)
orderByCallExpression = expressionTreeHelper.CallThenBy(orderByCallExpression, predicateI);
}
// Get result
var orderedList = (Func<IOrderedEnumerable<DiagnosticDetails>>)Expression.Lambda(orderByCallExpression).Compile();
return orderedList().ToList();
}
/// <remarks>
/// Look at for more details
/// </remarks>
public class ExpressionTreeHelper : IExpressionTreeHelper
{
public Expression CallElementAt(Expression collection, ConstantExpression constant)
{
Type cType = GetIEnumerableImpl(collection.Type);
collection = Expression.Convert(collection, cType);
Type elemType = cType.GetGenericArguments()[0];
// Enumerable.ElementAt<T>(IEnumerable<T>, int index)
MethodInfo elementAtMethod = (MethodInfo)GetGenericMethod(
typeof(Enumerable),
"ElementAt",
new[] { elemType },
new[] { collection.Type, constant.Type }, BindingFlags.Static);
return Expression.Call(
elementAtMethod,
collection,
constant);
}
public Expression CallOrderBy(Expression collection, Delegate predicate)
{
Type cType = GetIEnumerableImpl(collection.Type);
collection = Expression.Convert(collection, cType);
Type elemType = cType.GetGenericArguments()[0];
Type predType = typeof(Func<,>).MakeGenericType(elemType, predicate.Method.ReturnType);
// Enumerable.OrderBy<TSource, TKey>(IEnumerable<TSource>, Func<TSource,TKey>)
MethodInfo orderByMethod = (MethodInfo)
GetGenericMethod(
typeof(Enumerable),
"OrderBy",
new[] { elemType, predicate.Method.ReturnType },
new[] { cType, predType }, BindingFlags.Static);
return Expression.Call(
orderByMethod,
collection,
Expression.Constant(predicate));
}
public Expression CallThenBy(Expression collection, Delegate predicate)
{
Type inputType = GetIEnumerableImpl(collection.Type);
Type elemType = inputType.GetGenericArguments()[0];
Type predType = typeof(Func<,>).MakeGenericType(elemType, predicate.Method.ReturnType);
// ! important convert to IOrderedEnumerable
Type cType = typeof(IOrderedEnumerable<>).MakeGenericType(new Type[] { elemType });
collection = Expression.Convert(collection, cType);
// Enumerable.CallThenBy<TSource, TKey>(IEnumerable<TSource>, Func<TSource,TKey>)
MethodInfo thenByMethod = (MethodInfo)
GetGenericMethod(
typeof(Enumerable),
"ThenBy",
new[] { elemType, predicate.Method.ReturnType },
new[] { cType, predType }, BindingFlags.Static);
return Expression.Call(
thenByMethod,
collection,
Expression.Constant(predicate));
}
private MethodBase GetGenericMethod(Type type, string name, Type[] typeArgs, Type[] argTypes, BindingFlags flags)
{
int typeArity = typeArgs.Length;
var methods = type.GetMethods()
.Where(m => m.Name == name)
.Where(m => m.GetGenericArguments().Length == typeArity)
.Select(m => m.MakeGenericMethod(typeArgs));
return Type.DefaultBinder.SelectMethod(flags, methods.ToArray(), argTypes, null);
}
private bool IsIEnumerable(Type type)
{
return type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}
private Type GetIEnumerableImpl(Type type)
{
// Get IEnumerable implementation. Either type is IEnumerable<T> for some T,
// or it implements IEnumerable<T> for some T. We need to find the interface.
if (IsIEnumerable(type))
return type;
Type[] t = type.FindInterfaces((m, o) => IsIEnumerable(m), null);
Debug.Assert(t.Length == 1);
return t[0];
}
}
这样使用:
var sortedDetails = SortByAllValuesConsistently(diagnosticDetails)
我有这个类:
public class DiagnosticDetails
{
public string PrimaryValue { get; set; }
public Guid? Id { get; set; }
public IEnumerable<DiagnosticValues> Values { get; set; }
}
public class DiagnosticValues
{
public string Type { get; set; }
public string Value { get; set; }
}
我想为 Values
中的每个字符串构造动态 OrderBy().ThenBy()...
(此列表可以包含不同数量的字符串)因此它应该如下所示:
diagnosticDetails
.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim())
.ThenBy(detail => detail.Values.ElementAt(1).Value?.Trim())
.ThenBy(detail => detail.Values.ElementAt(2).Value?.Trim())
...
但我无法将其转换为表达式树:
var result = diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim());
//var expressionTreeForResult = ???
这是我的代码:
private List<DiagnosticDetails> SortByValue(List<DiagnosticDetails> diagnosticDetails)
{
IQueryable<DiagnosticDetails> queryableData = diagnosticDetails.AsQueryable();
ParameterExpression pe = Expression.Parameter(typeof(DiagnosticDetails), "detail");
//var predicate = diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim());
//var predicateExpressionTree = ???
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<DiagnosticDetails, string>>(predicateExpressionTree, new ParameterExpression[] { pe }));
var maxElementsInValues = diagnosticDetails.Select(dd => dd.Values.Count()).Max();
for (int i = 1; i < maxElementsInValues; i++)
{
orderByCallExpression = Expression.Call(
typeof(Queryable),
"ThenBy",
new Type[] { queryableData.ElementType },
orderByCallExpression,
Expression.Lambda<Func<DiagnosticDetails, string>>(predicateExpressionTree, new ParameterExpression[] { pe }));
}
var sortedDiagnosticDetails = Expression.Lambda<List<DiagnosticDetails>>(orderByCallExpression).Compile();
return sortedDiagnosticDetails;
}
如何将.OrderBy(detail => detail.Values.ElementAt(0).Value?.Trim())
转化为表达式树?
正如 Ivan Stoev 在评论中提到的,在这种特殊情况下无需创建表达式树。 实施比较方法会容易得多:
public static int CompareDiagnosticDetailsByValuesConsistently(DiagnosticDetails dd1, DiagnosticDetails dd2)
{
var maxDimension = Math.Max(dd1.Values.Count(), dd2.Values.Count());
for (int i = 0; i < maxDimension; i++)
{
if (dd1.Values.ElementAtOrDefault(i)?.Value == null)
{
if (dd2.Values.ElementAtOrDefault(i)?.Value == null)
continue;
return 1;
}
int result = dd1.Values.ElementAt(i).Value.CompareTo(dd2.Values.ElementAt(i).Value);
if (result == 0)
continue;
return result;
}
return 0;
}
并这样使用它:
diagnosticDetails.Sort(CompareDiagnosticDetailsByValuesConsistently);
但如果您仍想使用表达式树,此代码可满足您的需要:
private List<DiagnosticDetails> SortByAllValuesConsistently(List<DiagnosticDetails> diagnosticDetails)
{
/*
diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value)
.ThenBy(detail => detail.Values.ElementAt(1).Value))
.ThenBy(detail => detail.Values.ElementAt(2).Value))
.ThenBy(detail => detail.Values.ElementAt(3).Value))
...
for each value in detail.Values.
*/
if (diagnosticDetails.IsNullOrEmpty())
return diagnosticDetails;
IQueryable<DiagnosticDetails> queryableData = diagnosticDetails.AsQueryable();
// detail.Values
ParameterExpression p = Expression.Parameter(typeof(DiagnosticDetails), "detail");
MemberExpression prVs = Expression.Property(p, "Values");
// detail.Values.ElementAt(0).
ConstantExpression c0 = Expression.Constant(0, typeof(int));
Expression callElAt = expressionTreeHelper.CallElementAt(prVs, c0);
// detail.Values.ElementAt(0).Value
MemberExpression prV = Expression.Property(callElAt, "Value");
// detail => detail.Values.ElementAt(0).Value
Delegate predicate = Expression.Lambda(prV,p).Compile();
// diagnosticDetails.OrderBy(detail => detail.Values.ElementAt(0).Value)
Expression orderByCallExpression = expressionTreeHelper.CallOrderBy(queryableData.Expression, predicate);
var maxElementsInValues = diagnosticDetails.Select(dd => dd.Values.Count()).Max();
for (int i = 1; i < maxElementsInValues; i++)
{
// detail.Values
ParameterExpression pi = Expression.Parameter(typeof(DiagnosticDetails), "detail");
MemberExpression prVsi = Expression.Property(pi, "Values");
// detail.Values.ElementAt(i).
ConstantExpression ci = Expression.Constant(i, typeof(int));
Expression callElAti = expressionTreeHelper.CallElementAt(prVsi, ci);
// detail.Values.ElementAt(i).Value
MemberExpression prVi = Expression.Property(callElAti, "Value");
// detail => detail.Values.ElementAt(i).Value
Delegate predicateI = Expression.Lambda(prVi, pi).Compile();
// orderByCallExpression.ThenBy(detail => detail.Values.ElementAt(0).Value)
orderByCallExpression = expressionTreeHelper.CallThenBy(orderByCallExpression, predicateI);
}
// Get result
var orderedList = (Func<IOrderedEnumerable<DiagnosticDetails>>)Expression.Lambda(orderByCallExpression).Compile();
return orderedList().ToList();
}
/// <remarks>
/// Look at for more details
/// </remarks>
public class ExpressionTreeHelper : IExpressionTreeHelper
{
public Expression CallElementAt(Expression collection, ConstantExpression constant)
{
Type cType = GetIEnumerableImpl(collection.Type);
collection = Expression.Convert(collection, cType);
Type elemType = cType.GetGenericArguments()[0];
// Enumerable.ElementAt<T>(IEnumerable<T>, int index)
MethodInfo elementAtMethod = (MethodInfo)GetGenericMethod(
typeof(Enumerable),
"ElementAt",
new[] { elemType },
new[] { collection.Type, constant.Type }, BindingFlags.Static);
return Expression.Call(
elementAtMethod,
collection,
constant);
}
public Expression CallOrderBy(Expression collection, Delegate predicate)
{
Type cType = GetIEnumerableImpl(collection.Type);
collection = Expression.Convert(collection, cType);
Type elemType = cType.GetGenericArguments()[0];
Type predType = typeof(Func<,>).MakeGenericType(elemType, predicate.Method.ReturnType);
// Enumerable.OrderBy<TSource, TKey>(IEnumerable<TSource>, Func<TSource,TKey>)
MethodInfo orderByMethod = (MethodInfo)
GetGenericMethod(
typeof(Enumerable),
"OrderBy",
new[] { elemType, predicate.Method.ReturnType },
new[] { cType, predType }, BindingFlags.Static);
return Expression.Call(
orderByMethod,
collection,
Expression.Constant(predicate));
}
public Expression CallThenBy(Expression collection, Delegate predicate)
{
Type inputType = GetIEnumerableImpl(collection.Type);
Type elemType = inputType.GetGenericArguments()[0];
Type predType = typeof(Func<,>).MakeGenericType(elemType, predicate.Method.ReturnType);
// ! important convert to IOrderedEnumerable
Type cType = typeof(IOrderedEnumerable<>).MakeGenericType(new Type[] { elemType });
collection = Expression.Convert(collection, cType);
// Enumerable.CallThenBy<TSource, TKey>(IEnumerable<TSource>, Func<TSource,TKey>)
MethodInfo thenByMethod = (MethodInfo)
GetGenericMethod(
typeof(Enumerable),
"ThenBy",
new[] { elemType, predicate.Method.ReturnType },
new[] { cType, predType }, BindingFlags.Static);
return Expression.Call(
thenByMethod,
collection,
Expression.Constant(predicate));
}
private MethodBase GetGenericMethod(Type type, string name, Type[] typeArgs, Type[] argTypes, BindingFlags flags)
{
int typeArity = typeArgs.Length;
var methods = type.GetMethods()
.Where(m => m.Name == name)
.Where(m => m.GetGenericArguments().Length == typeArity)
.Select(m => m.MakeGenericMethod(typeArgs));
return Type.DefaultBinder.SelectMethod(flags, methods.ToArray(), argTypes, null);
}
private bool IsIEnumerable(Type type)
{
return type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}
private Type GetIEnumerableImpl(Type type)
{
// Get IEnumerable implementation. Either type is IEnumerable<T> for some T,
// or it implements IEnumerable<T> for some T. We need to find the interface.
if (IsIEnumerable(type))
return type;
Type[] t = type.FindInterfaces((m, o) => IsIEnumerable(m), null);
Debug.Assert(t.Length == 1);
return t[0];
}
}
这样使用:
var sortedDetails = SortByAllValuesConsistently(diagnosticDetails)