转换表达式并将其应用于 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);

在这种情况下,代码必须:

  1. 找出有问题的 属性 被命名为 "Manufacturer" 并从 key = "Manufacturer"
  2. 的字典中提取相关值
  3. 传递或以其他方式转换表达式,以便 ICustomCollection 字典值可以实际识别; typeof(Manufacturer) = U = string。因此,在我的 x.Manufacturer.IndexOf(...) 示例中,"transformed" 表达式实际上不再需要 x.Manufacturer,因为它未存储在查找中。
  4. 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);