通过LINQ转换并应用表达式?

时间:2019-06-20 23:25:40

标签: c# linq .net-4.5

我希望支持“ 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. 找出该属性命名为“制造商”,并将相关值从key = "Manufacturer"的字典中拉出
  2. 传递或转换表达式,以便ICustomCollection字典值可以实际识别; typeof(Manufacturer) = U = string。因此,在我的x.Manufacturer.IndexOf(...)示例中,“转换后的”表达式实际上不再需要x.Manufacturer,因为该表达式未存储在查找中。
  3. ICustomCollection正在使用的查询上执行LINQ表达式

我从最上面的表达式中拉出了表达式主体,以使其脱离MethodInfo,并且可以找到正确的字典键值对,但是我不知道如何转换LINQ表达式,因此我可以将其应用于“最低”级别_backingLookup:我尝试执行以下操作:

foreach(var kvp in _backingLookup)
{
   if(...need to apply LINQ expression here... == true)
   {
        // Add _internalLookup[kvp.Key] to return value
   }
}

我只是不知道如何在if中指出的地方应用LINQ表达式。想法?

1 个答案:

答案 0 :(得分:1)

使用名为ExpressionVisitor的通用Replace,一旦获得了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);