是否可以缓存在lambda表达式中计算出的值?
在以下代码的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); } }
解决方案
回答
我们不能简单地在一个单独的静态函数中编写布尔表达式,然后通过p.Ingredients作为参数从lambda调用它。
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次" p.Ingredients"变量,并将该变量保持在堆栈,寄存器或者所用寄存器附近。
回答
动荡的智力有完全正确的答案。
我只想建议我们可以从正在使用的类型中剥离一些null和异常,以使其更友好地使用它们。
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,为什么要只读?
回答
我们考虑过记忆吗?
基本思想是这样;如果我们有昂贵的函数调用,则有一个函数将在第一次调用时计算出昂贵的值,但此后返回缓存的版本。该函数看起来像这样;
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());
作为一般策略,这可能会有所帮助。
回答
好吧,在这种情况下,如果我们不能使用Memoization,则会受到限制,因为我们实际上只能使用堆栈作为缓存:我们无法在需要的范围内声明新变量。我所能想到的(并且我并不是说它会很漂亮)可以做我们想要的但保留所需的可组合性,就像...
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()的价格。
需要注意的是,在另一个示例中,原始示例中将没有任何内联,因为据我所知,表达式编译器没有进行该级别的优化。