15 是否可以缓存在 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);
    }
}
请先 登录 后评论

4 个回答

Fake Jim

你不能简单地将布尔表达式写在你从你的 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;
}
请先 登录 后评论
Rudolf Olah

在这种情况下我会说不。我假设编译器可以计算出它使用 p.Ingredients 变量 3 次,并将变量保持在堆栈或寄存器或它使用的任何东西附近。

请先 登录 后评论
Amy B

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,为什么是只读的?

请先 登录 后评论
Fake Jim

好吧,在这种情况下,如果你不能使用记忆,你就会受到相当的限制,因为你实际上只能将堆栈用作缓存:你无法在你的范围内声明一个新变量'会需要。我能想到的(我并不是说它会很漂亮)会做你想做的,但保留你需要的可组合性就像......

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() 的成本。

请注意,正如另一个答案所建议的那样,原始示例中不会有任何内联,因为据我所知,表达式编译器没有进行该级别的优化。

请先 登录 后评论
  • 17 关注
  • 0 收藏,409 浏览
  • ilitirit 提出于 2022-09-27 04:12