将动态创建的 lambda 应用于对象实例
Apply a dynamically created lambda to an object instance
我有一些代码可以从字符串开始动态创建一个 lambda。例如,我有一个过滤器 class,如下所示:
public class Criteria {
public string Property { get; set; }
public string Operator { get; set; }
public string Value { get; set; }
}
而且我能够从像这样的 Criteria 实例开始创建像 x => x.Name == "Foo"
这样的 lambda
Criteria c = new Criteria() {
Property = "Name",
Operator = "equal",
Value = "Foo"
}
假设有一个class喜欢
public class Receipt {
public string Name { get; set; }
public int Amount { get; set; }
[other props omitted]
public ICollection<ReceiptDetail> Details { get; set; }
}
我愿意:
- 将 lambda 应用于任何对象(我知道应该使用 Receipt 的 ParameterExpression 创建 lambda class)
- 取回 lambda 的布尔结果(例如名称等于 Foo 吗?)
- 将相同的逻辑应用于集合 Count() 方法(例如构建一个 lambda 来检查 receipt.Details.Count()
这可能吗?
编辑:根据评论,我正在详细说明我的需求。这段代码将让我有机会回答我的要求:如果为我的对象指定了规则,那么应用程序的行为应该有所不同。虽然这是一个常见的要求,但我想创建一个代码,允许我在添加更多规则时扩展它。其实我只有5种规则类型:
- 验证输入是否在一周中的特定日期
- 验证输入是否在特定时间范围内
- 验证输入的字段 "X" 是否为 less/equal/greather 而不是值
- 验证输入的字段"Y"是否包含值
- 验证作为集合的输入字段 "Z" 的计数是否 less/equal/greather 大于值
对于前 4 点,我已经能够使用 中的代码动态创建 lambda 表达式,我可以使用规范模式将其应用于对象本身。
最后一点需要以几乎相同的方式实现,但唯一的区别是表达式的左侧部分是方法调用而不是 属性(特别是 ICollection<T>.Count()
方法)
您可以对泛型使用反射。解决问题的一种方法可能是扩展
public static class EnumerableExtensions
{
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Criteria c)
{
var sourceType = typeof(T);
var propertyMember = sourceType.GetProperty(c.Property);
Func<string, bool> predicate = null;
switch (c.Operator)
{
case "equal":
predicate = (v) => v == c.Value;
break;
// other operators
default:
throw new ArgumentException("Unsupported operator.");
}
return source.Where(v => predicate((string)propertyMember.GetMethod.Invoke(v, null)));
}
}
您可以在代码中使用:
void FooBar()
{
Criteria c = new Criteria()
{
Property = "Name",
Operator = "equal",
Value = "foo"
};
var source = new Receipt[2];
source[0] = new Receipt { Name = "foo", Amount = 1 };
source[1] = new Receipt { Name = "bar", Amount = 2 };
var result = source.Where(c);
}
这只是给你一个想法。改进将是错误处理(属性 未找到、无效转换、空值等)、重构以启用单元测试(例如注入 select "strategy")和性能(例如构建、编译和缓存表达式树而不是反射)。这应该给你足够的关键字来了解。希望这有帮助。
这里有一些可以帮助您入门的东西。有很大的改进空间。尤其是丑陋的工厂。该演示旨在展示如何使用表达式来解决问题,而不是作为最佳实践或工厂模式演示。如果有任何不清楚的地方,请随时要求澄清。
用法
[Test]
public void ApplySameLogicToCollectionsCount()
{
var receipt = new Receipt();
var details = new ReceiptDetail();
var details2 = new ReceiptDetail();
receipt.Details.Add(details);
receipt.Details.Add(details2);
var result = LambdaGeneratorFactory<ICollection<ReceiptDetail>>.Run(detailsCount);
Assert.IsTrue(result(receipt.Details));
}
工厂
public static class LambdaGeneratorFactory<T>
{
//This is an ugly implementation of a Factory pattern.
//You should improve this possibly with interfaces, maybe abstract factory. I'd start with an ICriteria.
public static Predicate<T> Run(Criteria criteria)
{
if (typeof(T) == typeof (Receipt))
{
return CreateLambda(criteria);
}
else if (typeof (T) == typeof (ICollection<ReceiptDetail>))
{
return CreateLambdaWithCount(criteria);
}
return null;
}
private static Predicate<T> CreateLambda(Criteria criteria)
{
ParameterExpression pe = Expression.Parameter(typeof(T), "i");
Expression left = Expression.Property(pe, typeof(T).GetProperty(criteria.Property));
Expression right = Expression.Constant(criteria.Value);
Expression predicateBody = Expression.Equal(left, right);
var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();
return predicate;
}
private static Predicate<T> CreateLambdaWithCount(Criteria criteria)
{
ParameterExpression pe = Expression.Parameter(typeof(T), "i");
Expression count = Expression.Property(pe, typeof(T).GetProperty("Count"));
Expression left = Expression.Call(count, typeof(Object).GetMethod("ToString"));
Expression right = Expression.Constant(criteria.Value);
Expression predicateBody = Expression.Equal(left, right);
var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();
return predicate;
}
}
条件
private Criteria detailsCount = new Criteria()
{
Property = "Details",
Operator = "equal",
Value = "2"
};
切换到 ICriteria
,事情会变得更干净。一个更好的工厂,不需要 ToString
。编程到接口。
综上所述,这段代码感觉有点古怪。从字符串生成函数有什么意义?我感觉这正朝着从语法生成 C# 的方向发展。我不相信这会很好地扩展。对于非平凡的实现,请考虑 lex/yacc first. You can find more details for doing this in the Pragmatic Programmer "Implementing a mini language".
你的问题很有趣,我想了解一下要求。我创建了一个演示,我想知道该演示与您想要完成的有何不同?这里也有一个工作版本https://dotnetfiddle.net/AEBZ1w。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
public class Program
{
public static void Main()
{
Criteria c = new Criteria() {
Property = "Name",
Operator = "==",
Value = "Foo" };
var queryable = (new List<Receipt>() {
new Receipt { Name = "Foo", Amount = 1 },
new Receipt { Name = "Foo", Amount = 2 },
new Receipt { Name = "Bar" }
}).AsQueryable();
var parameter = Expression.Parameter(typeof(Receipt), "x");
var property = Expression.Property(parameter, typeof(Receipt).GetProperty(c.Property));
var constant = Expression.Constant(c.Value);
var operation = Expression.Equal(property, constant);
var expression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryable.ElementType },
queryable.Expression,
Expression.Lambda<Func<Receipt, bool>>(operation, new ParameterExpression[] { parameter })
);
Console.WriteLine("Linq Expression: {0} \n", expression.ToString());
Console.WriteLine("Results: \n");
var results = queryable.Provider.CreateQuery<Receipt>(expression);
foreach(var r in results)
{
Console.WriteLine("{0}:{1}", r.Name, r.Amount);
}
}
}
public class Criteria
{
public string Property, Operator, Value;
}
public class ReceiptDetail
{
public string ItemName;
}
public class Receipt
{
public string Name { get; set; }
public int Amount;
public ICollection<ReceiptDetail> Details;
}
参考资料
我有一些代码可以从字符串开始动态创建一个 lambda。例如,我有一个过滤器 class,如下所示:
public class Criteria {
public string Property { get; set; }
public string Operator { get; set; }
public string Value { get; set; }
}
而且我能够从像这样的 Criteria 实例开始创建像 x => x.Name == "Foo"
这样的 lambda
Criteria c = new Criteria() {
Property = "Name",
Operator = "equal",
Value = "Foo"
}
假设有一个class喜欢
public class Receipt {
public string Name { get; set; }
public int Amount { get; set; }
[other props omitted]
public ICollection<ReceiptDetail> Details { get; set; }
}
我愿意:
- 将 lambda 应用于任何对象(我知道应该使用 Receipt 的 ParameterExpression 创建 lambda class)
- 取回 lambda 的布尔结果(例如名称等于 Foo 吗?)
- 将相同的逻辑应用于集合 Count() 方法(例如构建一个 lambda 来检查 receipt.Details.Count()
这可能吗?
编辑:根据评论,我正在详细说明我的需求。这段代码将让我有机会回答我的要求:如果为我的对象指定了规则,那么应用程序的行为应该有所不同。虽然这是一个常见的要求,但我想创建一个代码,允许我在添加更多规则时扩展它。其实我只有5种规则类型:
- 验证输入是否在一周中的特定日期
- 验证输入是否在特定时间范围内
- 验证输入的字段 "X" 是否为 less/equal/greather 而不是值
- 验证输入的字段"Y"是否包含值
- 验证作为集合的输入字段 "Z" 的计数是否 less/equal/greather 大于值
对于前 4 点,我已经能够使用
最后一点需要以几乎相同的方式实现,但唯一的区别是表达式的左侧部分是方法调用而不是 属性(特别是 ICollection<T>.Count()
方法)
您可以对泛型使用反射。解决问题的一种方法可能是扩展
public static class EnumerableExtensions
{
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Criteria c)
{
var sourceType = typeof(T);
var propertyMember = sourceType.GetProperty(c.Property);
Func<string, bool> predicate = null;
switch (c.Operator)
{
case "equal":
predicate = (v) => v == c.Value;
break;
// other operators
default:
throw new ArgumentException("Unsupported operator.");
}
return source.Where(v => predicate((string)propertyMember.GetMethod.Invoke(v, null)));
}
}
您可以在代码中使用:
void FooBar()
{
Criteria c = new Criteria()
{
Property = "Name",
Operator = "equal",
Value = "foo"
};
var source = new Receipt[2];
source[0] = new Receipt { Name = "foo", Amount = 1 };
source[1] = new Receipt { Name = "bar", Amount = 2 };
var result = source.Where(c);
}
这只是给你一个想法。改进将是错误处理(属性 未找到、无效转换、空值等)、重构以启用单元测试(例如注入 select "strategy")和性能(例如构建、编译和缓存表达式树而不是反射)。这应该给你足够的关键字来了解。希望这有帮助。
这里有一些可以帮助您入门的东西。有很大的改进空间。尤其是丑陋的工厂。该演示旨在展示如何使用表达式来解决问题,而不是作为最佳实践或工厂模式演示。如果有任何不清楚的地方,请随时要求澄清。
用法
[Test]
public void ApplySameLogicToCollectionsCount()
{
var receipt = new Receipt();
var details = new ReceiptDetail();
var details2 = new ReceiptDetail();
receipt.Details.Add(details);
receipt.Details.Add(details2);
var result = LambdaGeneratorFactory<ICollection<ReceiptDetail>>.Run(detailsCount);
Assert.IsTrue(result(receipt.Details));
}
工厂
public static class LambdaGeneratorFactory<T>
{
//This is an ugly implementation of a Factory pattern.
//You should improve this possibly with interfaces, maybe abstract factory. I'd start with an ICriteria.
public static Predicate<T> Run(Criteria criteria)
{
if (typeof(T) == typeof (Receipt))
{
return CreateLambda(criteria);
}
else if (typeof (T) == typeof (ICollection<ReceiptDetail>))
{
return CreateLambdaWithCount(criteria);
}
return null;
}
private static Predicate<T> CreateLambda(Criteria criteria)
{
ParameterExpression pe = Expression.Parameter(typeof(T), "i");
Expression left = Expression.Property(pe, typeof(T).GetProperty(criteria.Property));
Expression right = Expression.Constant(criteria.Value);
Expression predicateBody = Expression.Equal(left, right);
var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();
return predicate;
}
private static Predicate<T> CreateLambdaWithCount(Criteria criteria)
{
ParameterExpression pe = Expression.Parameter(typeof(T), "i");
Expression count = Expression.Property(pe, typeof(T).GetProperty("Count"));
Expression left = Expression.Call(count, typeof(Object).GetMethod("ToString"));
Expression right = Expression.Constant(criteria.Value);
Expression predicateBody = Expression.Equal(left, right);
var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();
return predicate;
}
}
条件
private Criteria detailsCount = new Criteria()
{
Property = "Details",
Operator = "equal",
Value = "2"
};
切换到 ICriteria
,事情会变得更干净。一个更好的工厂,不需要 ToString
。编程到接口。
综上所述,这段代码感觉有点古怪。从字符串生成函数有什么意义?我感觉这正朝着从语法生成 C# 的方向发展。我不相信这会很好地扩展。对于非平凡的实现,请考虑 lex/yacc first. You can find more details for doing this in the Pragmatic Programmer "Implementing a mini language".
你的问题很有趣,我想了解一下要求。我创建了一个演示,我想知道该演示与您想要完成的有何不同?这里也有一个工作版本https://dotnetfiddle.net/AEBZ1w。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
public class Program
{
public static void Main()
{
Criteria c = new Criteria() {
Property = "Name",
Operator = "==",
Value = "Foo" };
var queryable = (new List<Receipt>() {
new Receipt { Name = "Foo", Amount = 1 },
new Receipt { Name = "Foo", Amount = 2 },
new Receipt { Name = "Bar" }
}).AsQueryable();
var parameter = Expression.Parameter(typeof(Receipt), "x");
var property = Expression.Property(parameter, typeof(Receipt).GetProperty(c.Property));
var constant = Expression.Constant(c.Value);
var operation = Expression.Equal(property, constant);
var expression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryable.ElementType },
queryable.Expression,
Expression.Lambda<Func<Receipt, bool>>(operation, new ParameterExpression[] { parameter })
);
Console.WriteLine("Linq Expression: {0} \n", expression.ToString());
Console.WriteLine("Results: \n");
var results = queryable.Provider.CreateQuery<Receipt>(expression);
foreach(var r in results)
{
Console.WriteLine("{0}:{1}", r.Name, r.Amount);
}
}
}
public class Criteria
{
public string Property, Operator, Value;
}
public class ReceiptDetail
{
public string ItemName;
}
public class Receipt
{
public string Name { get; set; }
public int Amount;
public ICollection<ReceiptDetail> Details;
}
参考资料