Domanda

Nel metodo ContieneIngredients nel codice seguente, è possibile memorizzare nella cache il file p.Ingredienti valore invece di farvi riferimento esplicitamente più volte?Questo è un esempio abbastanza banale che ho appena preparato a scopo illustrativo, ma il codice su cui sto lavorando fa riferimento a valori profondi P per esempio. p.InnerObject.ExpensiveMethod().Valore

modificare:Sto utilizzando PredicateBuilder da 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);
    }
}
È stato utile?

Soluzione

Non puoi semplicemente scrivere la tua espressione booleana in una funzione statica separata che chiami dal tuo lambda - passando p.Ingredients come parametro...

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

Altri suggerimenti

Hai considerato Memoizzazione?

L'idea di base è questa;se si dispone di una chiamata di funzione costosa, esiste una funzione che calcolerà il valore costoso alla prima chiamata, ma successivamente restituirà una versione memorizzata nella cache.La funzione è simile a questa;

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

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

    };
}

Ciò significa che puoi scrivere questo;

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

Come strategia generale, potrebbe aiutare.

Bene, in questo caso, se non puoi utilizzare Memoization, sei piuttosto limitato poiché puoi utilizzare solo lo stack come cache:Non hai modo di dichiarare una nuova variabile nell'ambito di cui avrai bisogno.Tutto quello a cui riesco a pensare (e non sto dicendo che sarà carino) che farà quello che vuoi ma manterrà la componibilità di cui hai bisogno sarebbe qualcosa del tipo...

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

Potresti combinare insieme i risultati di più chiamate TestWith in un'espressione booleana più complessa dove richiesto, memorizzando nella cache il valore costoso appropriato con ogni chiamata, oppure puoi nidificarli all'interno dei lambda passati come secondo parametro per gestire le tue complesse gerarchie profonde.

Sarebbe piuttosto difficile leggere il codice e poiché potresti introdurre molte più transizioni di stack con tutte le chiamate TestWith, se migliorerà le prestazioni dipenderà da quanto fosse costoso il tuo ExpensiveCall().

Come nota, non ci sarà alcun incorporamento nell'esempio originale come suggerito da un'altra risposta poiché il compilatore di espressioni non esegue quel livello di ottimizzazione, per quanto ne so.

Direi di no in questo caso.Presumo che il compilatore possa capire che utilizza il file p.Ingredients variabile 3 volte e manterrà la variabile vicina nello stack o nei registri o qualunque cosa usi.

Turbulent Intellect ha la risposta esattamente giusta.

Voglio solo consigliarti di eliminare alcuni valori nulli ed eccezioni dai tipi che stai utilizzando per renderne più semplice l'utilizzo.

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

Quindi, se hai i parametri di query in questa struttura...

Dictionary<string, List<string>> ingredients;

Puoi scrivere la query in questo modo.

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

PS, perché sola lettura?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top