- 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/
我想了解 Ruby 方法 methods() 是如何工作的。 我尝试使用“ruby 方法”在 Google 上搜索,但这不是我需要的。 我也看过 ruby-doc.org,但我没有找到这种方法。
Test 方法 对指定的字符串执行一个正则表达式搜索,并返回一个 Boolean 值指示是否找到匹配的模式。 object.Test(string) 参数 object 必选项。总是一个
Replace 方法 替换在正则表达式查找中找到的文本。 object.Replace(string1, string2) 参数 object 必选项。总是一个 RegExp 对象的名称。
Raise 方法 生成运行时错误 object.Raise(number, source, description, helpfile, helpcontext) 参数 object 应为
Execute 方法 对指定的字符串执行正则表达式搜索。 object.Execute(string) 参数 object 必选项。总是一个 RegExp 对象的名称。 string
Clear 方法 清除 Err 对象的所有属性设置。 object.Clear object 应为 Err 对象的名称。 说明 在错误处理后,使用 Clear 显式地清除 Err 对象。此
CopyFile 方法 将一个或多个文件从某位置复制到另一位置。 object.CopyFile source, destination[, overwrite] 参数 object 必选
Copy 方法 将指定的文件或文件夹从某位置复制到另一位置。 object.Copy destination[, overwrite] 参数 object 必选项。应为 File 或 F
Close 方法 关闭打开的 TextStream 文件。 object.Close object 应为 TextStream 对象的名称。 说明 下面例子举例说明如何使用 Close 方
BuildPath 方法 向现有路径后添加名称。 object.BuildPath(path, name) 参数 object 必选项。应为 FileSystemObject 对象的名称
GetFolder 方法 返回与指定的路径中某文件夹相应的 Folder 对象。 object.GetFolder(folderspec) 参数 object 必选项。应为 FileSy
GetFileName 方法 返回指定路径(不是指定驱动器路径部分)的最后一个文件或文件夹。 object.GetFileName(pathspec) 参数 object 必选项。应为
GetFile 方法 返回与指定路径中某文件相应的 File 对象。 object.GetFile(filespec) 参数 object 必选项。应为 FileSystemObject
GetExtensionName 方法 返回字符串,该字符串包含路径最后一个组成部分的扩展名。 object.GetExtensionName(path) 参数 object 必选项。应
GetDriveName 方法 返回包含指定路径中驱动器名的字符串。 object.GetDriveName(path) 参数 object 必选项。应为 FileSystemObjec
GetDrive 方法 返回与指定的路径中驱动器相对应的 Drive 对象。 object.GetDrive drivespec 参数 object 必选项。应为 FileSystemO
GetBaseName 方法 返回字符串,其中包含文件的基本名 (不带扩展名), 或者提供的路径说明中的文件夹。 object.GetBaseName(path) 参数 object 必
GetAbsolutePathName 方法 从提供的指定路径中返回完整且含义明确的路径。 object.GetAbsolutePathName(pathspec) 参数 object
FolderExists 方法 如果指定的文件夹存在,则返回 True;否则返回 False。 object.FolderExists(folderspec) 参数 object 必选项
FileExists 方法 如果指定的文件存在返回 True;否则返回 False。 object.FileExists(filespec) 参数 object 必选项。应为 FileS
我是一名优秀的程序员,十分优秀!