gpt4 book ai didi

c# - 如何创建一个通用方法来遍历对象的字段并将其用作 Where 谓词?

转载 作者:行者123 更新时间:2023-11-30 19:35:38 33 4
gpt4 key购买 nike

我正在构建一个通用接口(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();

[...]

}

然后我似乎必须动态调用 selectedFieldsExpExpression.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/

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com