Question

Dans la méthode ContainsIngredients du code suivant, est-il possible de mettre en cache la valeur p.Ingredients au lieu de la référencer explicitement plusieurs fois? Ceci est un exemple assez trivial que je viens de préparer à des fins d'illustration, mais le code sur lequel je travaille s'appuie sur des valeurs qui se trouvent profondément dans p , par exemple. p.InnerObject.ExpensiveMethod (). Value

modifier: J'utilise PredicateBuilder de http://www.albahari.com/nutshell/predicatebuilder.html

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);
    }
}
Était-ce utile?

La solution

Ne pouvez-vous pas simplement écrire votre expression booléenne dans une fonction statique distincte que vous appelez depuis votre lambda - en passant p.Ingredients en tant que paramètre ...

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;
}

Autres conseils

Avez-vous envisagé la Mémoization ?

L'idée de base est la suivante. si vous avez un appel de fonction coûteux, il existe une fonction qui calcule la valeur coûteuse au premier appel, mais renvoie une version en cache par la suite. La fonction ressemble à ceci;

static Func<T> Remember<T>(Func<T> GetExpensiveValue)
{
    bool isCached= false;
    T cachedResult = default(T);

    return () =>
    {
        if (!isCached)
        {
            cachedResult = GetExpensiveValue();
            isCached = true;
        }
        return cachedResult;

    };
}

Cela signifie que vous pouvez écrire ceci;

    // 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());

En général, cela pourrait aider.

Eh bien, dans ce cas, si vous ne pouvez pas utiliser Memoization, vous êtes plutôt limité car vous ne pouvez réellement utiliser la pile que comme cache: vous n'avez aucun moyen de déclarer une nouvelle variable à la portée que vous avez ' J'aurai besoin. Tout ce à quoi je peux penser (et je ne prétends pas que ce sera joli), fera ce que vous voulez, mais conservez la composabilité dont vous avez besoin, ce serait un peu comme ...

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;
}

Vous pouvez combiner les résultats de plusieurs appels TestWith en une expression booléenne plus complexe, le cas échéant (en mettant en cache la valeur onéreuse appropriée à chaque appel), ou vous pouvez les imbriquer dans les lambdas passés en tant que second paramètre pour traiter vos données complexes en profondeur. hiérarchies.

Cependant, il serait assez difficile de lire du code et puisque vous pourriez introduire beaucoup plus de transitions de pile avec tous les appels TestWith, l’amélioration des performances dépend du coût de votre ExpensiveCall ().

Comme note, il n’y aura pas d’insertion dans l’exemple original comme le suggère une autre réponse, car le compilateur d’expressions ne fait pas ce niveau d’optimisation à ma connaissance.

Je dirais non dans ce cas. Je suppose que le compilateur peut comprendre qu’il utilise la variable p.Ingredients 3 fois et qu’il gardera la variable à proximité de la pile, des registres ou quoi que ce soit qu’elle utilise.

Turbulent Intellect a exactement la bonne réponse.

Je tiens simplement à vous informer que vous pouvez supprimer certaines des valeurs null et des exceptions des types que vous utilisez afin de les rendre plus conviviales.

    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())    {    }

Ensuite, si vous avez les paramètres de requête dans cette structure ...

Dictionary<string, List<string>> ingredients;

Vous pouvez écrire la requête comme ceci.

from p in Potions
where ingredients.Any(i => i.Value.Any(v => p.IngredientBag.Has(i.Key, v))
select p;

PS, pourquoi en lecture seule?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top