是否可以缓存在 lambda 表达式中评估的值?
Posted
技术标签:
【中文标题】是否可以缓存在 lambda 表达式中评估的值?【英文标题】:Is it possible to cache a value evaluated in a lambda expression? 【发布时间】:2010-09-09 03:29:16 【问题描述】:在以下代码的 ContainsIngredients 方法中,是否可以缓存 p.Ingredients 值而不是多次显式引用它?这是一个相当简单的例子,我只是为了说明目的而编写的,但我正在处理的代码引用了 p 内部的值,例如。 p.InnerObject.ExpensiveMethod().Value
编辑: 我正在使用来自http://www.albahari.com/nutshell/predicatebuilder.html的 PredicateBuilder@
public class IngredientBag
private readonly Dictionary<string, string> _ingredients = new Dictionary<string, string>();
public void Add(string type, string name)
_ingredients.Add(type, name);
public string Get(string type)
return _ingredients[type];
public bool Contains(string type)
return _ingredients.ContainsKey(type);
public class Potion
public IngredientBag Ingredients get; private set;
public string Name get; private set;
public Potion(string name) : this(name, null)
public Potion(string name, IngredientBag ingredients)
Name = name;
Ingredients = ingredients;
public static Expression<Func<Potion, bool>>
ContainsIngredients(string ingredientType, params string[] ingredients)
var predicate = PredicateBuilder.False<Potion>();
// Here, I'm accessing p.Ingredients several times in one
// expression. Is there any way to cache this value and
// reference the cached value in the expression?
foreach (var ingredient in ingredients)
var temp = ingredient;
predicate = predicate.Or (
p => p.Ingredients != null &&
p.Ingredients.Contains(ingredientType) &&
p.Ingredients.Get(ingredientType).Contains(temp));
return predicate;
[STAThread]
static void Main()
var potions = new List<Potion>
new Potion("Invisibility", new IngredientBag()),
new Potion("Bonus"),
new Potion("Speed", new IngredientBag()),
new Potion("Strength", new IngredientBag()),
new Potion("Dummy Potion")
;
potions[0].Ingredients.Add("solid", "Eye of Newt");
potions[0].Ingredients.Add("liquid", "Gall of Peacock");
potions[0].Ingredients.Add("gas", "Breath of Spider");
potions[2].Ingredients.Add("solid", "Hair of Toad");
potions[2].Ingredients.Add("gas", "Peacock's anguish");
potions[3].Ingredients.Add("liquid", "Peacock Sweat");
potions[3].Ingredients.Add("gas", "Newt's aura");
var predicate = Potion.ContainsIngredients("solid", "Newt", "Toad")
.Or(Potion.ContainsIngredients("gas", "Spider", "Scorpion"));
foreach (var result in
from p in potions
where(predicate).Compile()(p)
select p)
Console.WriteLine(result.Name);
【问题讨论】:
【参考方案1】:你考虑过Memoization吗?
基本思路是这样的;如果你有一个昂贵的函数调用,有一个函数将在第一次调用时计算昂贵的值,但之后返回一个缓存版本。函数如下所示;
static Func<T> Remember<T>(Func<T> GetExpensiveValue)
bool isCached= false;
T cachedResult = default(T);
return () =>
if (!isCached)
cachedResult = GetExpensiveValue();
isCached = true;
return cachedResult;
;
这意味着你可以写这个;
// here's something that takes ages to calculate
Func<string> MyExpensiveMethod = () =>
System.Threading.Thread.Sleep(5000);
return "that took ages!";
;
// and heres a function call that only calculates it the once.
Func<string> CachedMethod = Remember(() => MyExpensiveMethod());
// only the first line takes five seconds;
// the second and third calls are instant.
Console.WriteLine(CachedMethod());
Console.WriteLine(CachedMethod());
Console.WriteLine(CachedMethod());
作为一般策略,它可能会有所帮助。
【讨论】:
这是我通常会做的事情,但在这种情况下,我无权访问源代码。我给出的示例仅用于说明目的【参考方案2】:您不能简单地将布尔表达式写在您从 lambda 调用的单独静态函数中 - 将 p.Ingredients 作为参数传递...
private static bool IsIngredientPresent(IngredientBag i, string ingredientType, string ingredient)
return i != null && i.Contains(ingredientType) && i.Get(ingredientType).Contains(ingredient);
public static Expression<Func<Potion, bool>>
ContainsIngredients(string ingredientType, params string[] ingredients)
var predicate = PredicateBuilder.False<Potion>();
// Here, I'm accessing p.Ingredients several times in one
// expression. Is there any way to cache this value and
// reference the cached value in the expression?
foreach (var ingredient in ingredients)
var temp = ingredient;
predicate = predicate.Or(
p => IsIngredientPresent(p.Ingredients, ingredientType, temp));
return predicate;
【讨论】:
我可以做到,但就像我说的,这是一个相当微不足道的例子。我正在处理的示例使用深度嵌套的值,通常向下几个级别并通过昂贵的方法调用获得。看来我将不得不诉诸拆分谓词... 我的错误。我已经删除了我的错误答案。【参考方案3】:好吧,在这种情况下,如果你不能使用记忆,你会受到相当的限制,因为你实际上只能将堆栈用作缓存:你无法在你的范围内声明一个新变量'会需要。我能想到的(我并不是说它会很漂亮)会做你想做的,但保留你需要的可组合性就像......
private static bool TestWith<T>(T cached, Func<T, bool> predicate)
return predicate(cached);
public static Expression<Func<Potion, bool>>
ContainsIngredients(string ingredientType, params string[] ingredients)
var predicate = PredicateBuilder.False<Potion>();
// Here, I'm accessing p.Ingredients several times in one
// expression. Is there any way to cache this value and
// reference the cached value in the expression?
foreach (var ingredient in ingredients)
var temp = ingredient;
predicate = predicate.Or (
p => TestWith(p.Ingredients,
i => i != null &&
i.Contains(ingredientType) &&
i.Get(ingredientType).Contains(temp));
return predicate;
您可以在需要时将多个 TestWith 调用的结果组合成一个更复杂的布尔表达式 - 为每个调用缓存适当的昂贵值 - 或者您可以将它们嵌套在作为第二个参数传递的 lambda 中,以处理复杂的深度层次结构。
但阅读代码会非常困难,而且由于您可能会在所有 TestWith 调用中引入更多堆栈转换,因此它是否会提高性能将取决于您的 ExpensiveCall() 的成本。
请注意,正如另一个答案所建议的那样,原始示例中不会有任何内联,因为据我所知,表达式编译器没有进行该级别的优化。
【讨论】:
【参考方案4】:在这种情况下我会说不。我假设编译器可以计算出它使用了 p.Ingredients
变量 3 次,并将变量保持在堆栈或寄存器或它使用的任何东西附近。
【讨论】:
【参考方案5】:Turbulent Intellect 给出了完全正确的答案。
我只是想建议您可以从您正在使用的类型中去除一些空值和异常,以便更友好地使用它们。
public class IngredientBag
private Dictionary<string, string> _ingredients =
new Dictionary<string, string>();
public void Add(string type, string name)
_ingredients[type] = name;
public string Get(string type)
return _ingredients.ContainsKey(type) ? _ingredients[type] : null;
public bool Has(string type, string name)
return name == null ? false : this.Get(type) == name;
public Potion(string name) : this(name, new IngredientBag())
那么,如果你有这个结构中的查询参数...
Dictionary<string, List<string>> ingredients;
你可以这样写查询。
from p in Potions
where ingredients.Any(i => i.Value.Any(v => p.IngredientBag.Has(i.Key, v))
select p;
PS,为什么是只读的?
【讨论】:
readonly 修饰符提示编译器 _ingredients 变量只被写入一次。它有助于编译器优化代码。以上是关于是否可以缓存在 lambda 表达式中评估的值?的主要内容,如果未能解决你的问题,请参考以下文章
lambda 表达式使用 select 和 where 子句连接多个表