我有一些代码可以从字符串开始动态创建lambda。例如,我有一个类似以下的过滤器类:
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"
}
假设有一个像
这样的课程public class Receipt {
public string Name { get; set; }
public int Amount { get; set; }
[other props omitted]
public ICollection<ReceiptDetail> Details { get; set; }
}
我想:
这可能吗?
编辑:根据评论,我正在详细阐述我的需求。这段代码让我有机会回答我的要求,并说:如果为我的对象指定了规则,那么应用程序的行为应该有所不同。虽然这是一个常见的要求,但我想创建一个代码,允许我扩展它,因为会添加更多规则。实际上我只有5种规则类型:
对于前4个点,我能够动态创建一个lambda表达式,其代码类似于P.Brian.Mackey answer,我可以使用规范模式将其应用于对象本身。
最后一点需要以相同的方式实现,但唯一的区别是表达式的左侧部分是方法调用而不是属性(特别是ICollection<T>.Count()
方法)
答案 0 :(得分:3)
这是让你入门的东西。还有很大的改进空间。特别是丑陋的工厂。此演示旨在说明如何使用表达式来解决问题,而不是作为最佳实践或工厂模式演示。如果有任何不清楚的地方,请随时要求澄清。
[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。您可以在Pragmatic Programmer“实施迷你语言”中找到有关执行此操作的更多详细信息。
答案 1 :(得分:1)
你的问题是一个引人入胜的问题,我想了解这些要求。我创建了一个演示,我想知道,演示与你想要完成的演示有什么不同?这里有一个工作版本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;
}
参考
答案 2 :(得分:0)
您可以使用泛型反射。解决问题的一种方法可能是扩展
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);
}
这只是为了给你一个想法。改进将是错误处理(未找到属性,无效转换,空值等),重构以启用单元测试(例如,注入选择“策略”)和性能(例如,构建,编译和缓存表达式树而不是反射)。这应该为您提供足够的关键词来了解。希望这会有所帮助。