- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在构建一个通用接口(interface)以从类中公开选定的字符串属性,然后我想在每个字段中搜索文本,以检查它是否匹配。
这是我的 IFieldExposer
接口(interface):
using System;
using System.Collections.Generic;
public interface IFieldExposer<T>
{
IEnumerable<Func<T, string>> GetFields();
}
现在,我在我的 DataClass
中这样实现它公开我想迭代的属性。请注意,我还公开了我的 ChildClass
中的一个属性。 :
using System;
using System.Collections.Generic;
class DataClass : IFieldExposer<DataClass>
{
public string PropertyOne { get; set; }
public string PropertyTwo { get; set; }
public ChildClass Child { get; set; }
public IEnumerable<Func<DataClass, string>> GetFields()
{
return new List<Func<DataClass, string>>
{
a => a.PropertyOne,
b => b.Child.PropertyThree
};
}
}
class ChildClass
{
public string PropertyThree { get; set; }
}
我还为 IFieldExposer<T>
创建了扩展方法因为我想保持简单并且能够简单地调用 obj.Match(text, ignoreCase)
我代码中的其他地方。此方法应该告诉我我的对象是否与我的文本匹配。这是 ExtensionClass
的代码,它没有按预期工作:
using System;
using System.Linq.Expressions;
using System.Reflection;
public static class ExtensionClass
{
public static bool Match<T>(this IFieldExposer<T> obj, string text, bool ignoreCase)
{
Func<bool> expression = Expression.Lambda<Func<bool>>(obj.CreateExpressionTree(text, ignoreCase)).Compile();
return expression();
}
private static Expression CreateExpressionTree<T>(this IFieldExposer<T> obj, string text, bool ignoreCase)
{
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
var exposedFields = obj.GetFields();
if (ignoreCase)
{
// How should I do convert these to lower too?
// exposedFields = exposedFields.Select(e => e.???.ToLower());
text = text.ToLower();
}
Expression textExp = Expression.Constant(text);
Expression orExpressions = Expression.Constant(false);
foreach (var field in exposedFields)
{
//How should I call the contains method on the string field?
Expression fieldExpression = Expression.Lambda<Func<string>>(Expression.Call(Expression.Constant(obj), field.Method)); //this doesn't work
Expression contains = Expression.Call(fieldExpression, containsMethod, textExp);
orExpressions = Expression.Or(orExpressions, contains);
}
return orExpressions;
}
}
请检查上面代码中的注释。我想知道如何将所有字符串属性转换为小写(如果需要)以及如何调用 string.Contains
在他们每个人中。创建 fieldExpression
时出现此错误:
Method 'System.String <GetFields>b__12_0(DataClass)' declared on type 'DataClass+<>c' cannot be called with instance of type 'DataClass'
我没有使用表达式树的经验。我花了几个小时阅读文档和其他类似问题的答案,但我仍然不明白如何实现我想要的……我不知道现在该做什么。
我正在控制台应用程序中对此进行测试,所以如果您想自己构建它,这里是主类:
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
var data = new DataClass
{
PropertyOne = "Lorem",
PropertyTwo = "Ipsum",
Child = new ChildClass
{
PropertyThree = "Dolor"
}
};
var dataList = new List<DataClass> { data };
var results = dataList.Where(d => d.Match("dolor", true));
}
}
我忘了说我的 dataList
应该是 IQueryable
我想在 SQL 中执行我的代码,这就是我尝试自己构建表达式树的原因。所以看起来我的示例代码应该是:
var dataList = new List<DataClass> { data };
var query = dataList.AsQueryable();
var results = query.Where(ExtensionClass.Match<DataClass>("lorem dolor"));
虽然我的方法变成了:(我遵循@sjb-sjb 的回答并将GetFields()
中的IFieldExposer<T>
方法更改为SelectedFields
属性)
public static Expression<Func<T, bool>> Match<T>(string text, bool ignoreCase) where T : IFieldExposer<T>
{
ParameterExpression parameter = Expression.Parameter(typeof(T), "obj");
MemberExpression selectedFieldsExp = Expression.Property(parameter, "SelectedFields");
LambdaExpression lambda = Expression.Lambda(selectedFieldsExp, parameter).Compile();
[...]
}
然后我似乎必须动态调用 selectedFieldsExp
与 Expression.Lambda
.我想到了:
Expression.Lambda(selectedFieldsExp, parameter).Compile();
这行得通,但我不知道如何正确调用 DynamicInvoke()
对于 lambda 表达式。
它抛出 Parameter count mismatch.
如果我不带参数调用它并且 Object of type 'System.Linq.Expressions.TypedParameterExpression' cannot be converted to type 'DataClass'.
如果我这样做DynamicInvoke(parameter).
有什么想法吗?
最佳答案
在开始实现之前,有一些设计缺陷需要修复。
首先,几乎所有查询提供程序(LINQ to Object 除外,它只是将 lambda 表达式编译为委托(delegate)并执行它们)不支持调用表达式和自定义(未知)方法。那是因为它们不执行表达式,而是将它们翻译成其他东西(例如 SQL),并且翻译是基于先验知识。
调用表达式的一个示例是 Func<...>
代表们。所以你应该做的第一件事是使用 Expression<Func<...>>
无论你现在有Func<...>
.
其次,查询表达式树是静态构建的,即没有可用于获取元数据的真实对象实例,因此 IFieldExposer<T>
的想法不会工作。您需要一个静态公开的表达式列表,如下所示:
class DataClass //: IFieldExposer<DataClass>
{
// ...
public static IEnumerable<Expression<Func<DataClass, string>>> GetFields()
{
return new List<Expression<Func<DataClass, string>>>
{
a => a.PropertyOne,
b => b.Child.PropertyThree
};
}
}
那么有问题的方法的签名可能是这样的
public static Expression<Func<T, bool>> Match<T>(
this IEnumerable<Expression<Func<T, string>>> fields, string text, bool ignoreCase)
像这样使用
var dataList = new List<DataClass> { data };
var query = dataList.AsQueryable()
.Where(DataClass.GetFields().Match("lorem", true));
现在执行。所需的表达式可以完全用 Expression
构建类方法,但我将向您展示一个更简单的(恕我直言)方法,该方法通过将参数替换为其他表达式来从编译时表达式组成表达式。
您只需要一个小的辅助工具方法,用另一个表达式替换 lambda 表达式参数:
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : base.VisitParameter(node);
}
}
在内部它使用 ExpressionVistor
找到传递的每个实例 ParameterExpression
并将其替换为传递的 Expression
.
使用这个辅助方法,实现可能是这样的:
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> Match<T>(this IEnumerable<Expression<Func<T, string>>> fields, string text, bool ignoreCase)
{
Expression<Func<string, bool>> match;
if (ignoreCase)
{
text = text.ToLower();
match = input => input.ToLower().Contains(text);
}
else
{
match = input => input.Contains(text);
}
// T source =>
var parameter = Expression.Parameter(typeof(T), "source");
Expression anyMatch = null;
foreach (var field in fields)
{
// a.PropertyOne --> source.PropertyOne
// b.Child.PropertyThree --> source.Child.PropertyThree
var fieldAccess = field.Body.ReplaceParameter(field.Parameters[0], parameter);
// input --> source.PropertyOne
// input --> source.Child.PropertyThree
var fieldMatch = match.Body.ReplaceParameter(match.Parameters[0], fieldAccess);
// matchA || matchB
anyMatch = anyMatch == null ? fieldMatch : Expression.OrElse(anyMatch, fieldMatch);
}
if (anyMatch == null) anyMatch = Expression.Constant(false);
return Expression.Lambda<Func<T, bool>>(anyMatch, parameter);
}
}
input => input.ToLower().Contains(text)
或 input => input.Contains(text)
是我们的编译时匹配表达式,然后我们将其替换为 input
参数与传递的主体 Expression<Func<T, string>>
lambda 表达式,其参数替换为最终表达式中使用的公共(public)参数。生成的 bool 表达式与 Expression.OrElse
组合这相当于 C# ||
运算符(而 Expression.Or
用于按位 |
运算符,通常不应与逻辑运算一起使用)。顺便说一句,&&
- 使用 Expression.AndAlso
而不是 Expression.And
这是按位 &
.
这个过程几乎相当于 string.Replace
的表达式.如果解释和代码注释不够,您可以逐步查看代码并查看确切的表达式转换和表达式构建过程。
关于c# - 如何创建一个通用方法来遍历对象的字段并将其用作 Where 谓词?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53463755/
我是一名优秀的程序员,十分优秀!