转换表达式并将其应用于 LINQ?
Tranform and Apply Expression via LINQ?
我希望支持 "expression passthru" 如下:
public IFastIndexingCollection<T> {
// Main data structure here =
// Key = property name
// Value = class below
private Dictionary<string, ICustomCollection<T, U>> _indexedItems;
public IEnumerable<T> FindWhere(Expression<Func<T, bool>> expression);
}
internal ICustomCollection<T, U> {
// Main data structure =
// T = type of data structure, e.g., Phone
// U = property type, e.g., string
// Key = value for given property (e.g., "Samsung")
// Value = List<T> of items matching that key
private ILookup<U, T> _backingLookup;
}
当我们试图将 LINQ 表达式传递给自定义列表时,麻烦就来了。假设用户执行:
<T> = Phone
FastIndexingCollection<Phone>.FindWhere(x => x.Manufacturer.IndexOf("Samsung") > -1);
在这种情况下,代码必须:
- 找出有问题的 属性 被命名为 "Manufacturer" 并从
key = "Manufacturer"
的字典中提取相关值
- 传递或以其他方式转换表达式,以便
ICustomCollection
字典值可以实际识别; typeof(Manufacturer) = U = string
。因此,在我的 x.Manufacturer.IndexOf(...)
示例中,"transformed" 表达式实际上不再需要 x.Manufacturer
,因为它未存储在查找中。
- 对
ICustomCollection
正在使用的查找执行 LINQ 表达式
我已经从最上面的表达式中取出表达式主体以从中获取 MethodInfo,并且可以获得正确的字典键值对,但我不知道如何转换 LINQ 表达式这样我就可以在 "lowest" 级别上应用它 _backingLookup
:我尝试做类似的事情:
foreach(var kvp in _backingLookup)
{
if(...need to apply LINQ expression here... == true)
{
// Add _internalLookup[kvp.Key] to return value
}
}
我只是不知道如何在 if 中指示的位置应用 LINQ 表达式。想法?
使用名为 Replace
的通用 ExpressionVisitor
,您可以在获得 MemberExpression
后转换测试,您需要获取 属性 或字段名称。
public static ExpressionExt {
/// <summary>
/// Replaces an Expression (reference Equals) with another Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
}
/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
这是 FindWhere
实现的开始部分,它演示了如何使用 Replace
:
public override IEnumerable<T> FindWhere(Expression<Func<T, bool>> testFn) {
var testBody = (BinaryExpression)testFn.Body;
var fldTestExpr = testBody.Left;
if (fldTestExpr.NodeType == ExpressionType.Call)
fldTestExpr = ((MethodCallExpression)fldTestExpr).Object;
if (fldTestExpr is MemberExpression me) {
var memberName = me.Member.Name;
var newp = Expression.Parameter(me.Type);
var newBody = testBody.Replace(me, newp);
var newLambda = Expression.Lambda(newBody, newp);
var newTestFn = newLambda.Compile();
var testans = (bool) newTestFn.DynamicInvoke("this Samsung that");
// using DynamicInvoke is not terrible efficient, but lacking a static
// type for the property means the compiler must use object
}
}
您可以使用将成员访问与测试分开的 FindWhere
版本来提高性能:
public override IEnumerable<T> FindWhere2<U>(Expression<Func<T, U>> accessExpr, Func<U, bool> testFn);
var ans = fic.FindWhere2(x => x.Manufacturer, y => y.IndexOf("Samsung") > -1);
我希望支持 "expression passthru" 如下:
public IFastIndexingCollection<T> {
// Main data structure here =
// Key = property name
// Value = class below
private Dictionary<string, ICustomCollection<T, U>> _indexedItems;
public IEnumerable<T> FindWhere(Expression<Func<T, bool>> expression);
}
internal ICustomCollection<T, U> {
// Main data structure =
// T = type of data structure, e.g., Phone
// U = property type, e.g., string
// Key = value for given property (e.g., "Samsung")
// Value = List<T> of items matching that key
private ILookup<U, T> _backingLookup;
}
当我们试图将 LINQ 表达式传递给自定义列表时,麻烦就来了。假设用户执行:
<T> = Phone
FastIndexingCollection<Phone>.FindWhere(x => x.Manufacturer.IndexOf("Samsung") > -1);
在这种情况下,代码必须:
- 找出有问题的 属性 被命名为 "Manufacturer" 并从
key = "Manufacturer"
的字典中提取相关值
- 传递或以其他方式转换表达式,以便
ICustomCollection
字典值可以实际识别;typeof(Manufacturer) = U = string
。因此,在我的x.Manufacturer.IndexOf(...)
示例中,"transformed" 表达式实际上不再需要x.Manufacturer
,因为它未存储在查找中。 - 对
ICustomCollection
正在使用的查找执行 LINQ 表达式
我已经从最上面的表达式中取出表达式主体以从中获取 MethodInfo,并且可以获得正确的字典键值对,但我不知道如何转换 LINQ 表达式这样我就可以在 "lowest" 级别上应用它 _backingLookup
:我尝试做类似的事情:
foreach(var kvp in _backingLookup)
{
if(...need to apply LINQ expression here... == true)
{
// Add _internalLookup[kvp.Key] to return value
}
}
我只是不知道如何在 if 中指示的位置应用 LINQ 表达式。想法?
使用名为 Replace
的通用 ExpressionVisitor
,您可以在获得 MemberExpression
后转换测试,您需要获取 属性 或字段名称。
public static ExpressionExt {
/// <summary>
/// Replaces an Expression (reference Equals) with another Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
}
/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
这是 FindWhere
实现的开始部分,它演示了如何使用 Replace
:
public override IEnumerable<T> FindWhere(Expression<Func<T, bool>> testFn) {
var testBody = (BinaryExpression)testFn.Body;
var fldTestExpr = testBody.Left;
if (fldTestExpr.NodeType == ExpressionType.Call)
fldTestExpr = ((MethodCallExpression)fldTestExpr).Object;
if (fldTestExpr is MemberExpression me) {
var memberName = me.Member.Name;
var newp = Expression.Parameter(me.Type);
var newBody = testBody.Replace(me, newp);
var newLambda = Expression.Lambda(newBody, newp);
var newTestFn = newLambda.Compile();
var testans = (bool) newTestFn.DynamicInvoke("this Samsung that");
// using DynamicInvoke is not terrible efficient, but lacking a static
// type for the property means the compiler must use object
}
}
您可以使用将成员访问与测试分开的 FindWhere
版本来提高性能:
public override IEnumerable<T> FindWhere2<U>(Expression<Func<T, U>> accessExpr, Func<U, bool> testFn);
var ans = fic.FindWhere2(x => x.Manufacturer, y => y.IndexOf("Samsung") > -1);