Frage

In dem ContainsIngredients Verfahren in dem folgenden Code ist es möglich, die p.Ingredients Wert statt explizit verweist es mehrmals cachen? Dies ist ein ziemlich einfaches Beispiel, das ich nur zu Veranschaulichungszwecken gekocht, aber der Code, den ich auf Referenzen bin Arbeitswerte tief in p zB. p.InnerObject.ExpensiveMethod (). Value

edit: Ich bin mit dem PredicateBuilder von 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);
    }
}
War es hilfreich?

Lösung

Sie können nicht einfach schreiben Sie Ihre boolean Ausdruck in einer separaten statische Funktion, die Sie von Ihrem Lambda nennen - p.Ingredients als Parameter übergeben ...

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

Andere Tipps

Haben Sie darüber nachgedacht, memoization ?

Die Grundidee ist dies; wenn Sie einen teueren Funktionsaufruf haben, gibt es eine Funktion, die den teueren Wert auf dem ersten Anruf berechnen, aber eine im Cache gespeicherte Version danach zurückzukehren. Die Funktion sieht wie folgt aus;

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

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

    };
}

Dies bedeutet, dass Sie diese schreiben kann;

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

Als allgemeine Strategie, es könnte helfen.

Nun, in diesem Fall, wenn Sie nicht memoization verwenden können, sind Sie eher eingeschränkt, da man wirklich nur den Stack als Cache verwenden kann: Sie haben keine Möglichkeit, bekommen eine neue Variable im Bereich, den Sie‘zu erklären ll Notwendigkeit. Ich kann nur denken (und ich behaupte nicht, wird es recht sein), die das tun, was Sie wollen, aber behalten die composability Sie so etwas wie ...

wäre müssen
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;
}

Sie können die Ergebnisse miteinander zu verknüpfen, von mehreren Testwith in eine komplexere Boolesche Ausdruck ruft bei Bedarf - bei jedem Aufruf der entsprechenden teuer Wert Cachen - oder Sie können sie Nest innerhalb der Lambda-Ausdrücke als zweiten Parameter übergeben mit dem Komplex tief beschäftigen Hierarchien.

Es wäre sehr schwierig sein, Code obwohl zu lesen und da Sie die Testwith nennt ein paar mehr Stapel Übergänge mit allen könnte die Einführung, ob es verbessert die Leistung auf, wie teuer Ihr ExpensiveCall abhängen würde () war.

Als Hinweis wird es kein inlining im ursprünglichen Beispiel sein, wie durch eine andere Antwort vorgeschlagen, da der Ausdruck Compiler nicht, dass Niveau der Optimierung macht, soweit ich weiß.

Ich würde sagen, nicht in diesem Fall. Ich gehe davon aus, dass der Compiler herausfinden können, dass sie die p.Ingredients Variable 3 mal verwendet und wird die Variable closeby auf den Stapel halten oder den Registern oder was auch immer es verwendet.

Turbulent Intelligenz hat die genau richtige Antwort.

Ich möchte nur darauf hinweisen, dass Sie einige der NULL-Werte und Ausnahmen von den Typen Streifen können Sie es freundlicher machen verwenden, um sie zu nutzen.

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

Wenn Sie dann die Abfrageparameter in dieser Struktur haben ...

Dictionary<string, List<string>> ingredients;

Sie können die Abfrage so schreiben.

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

PS, warum nur lesbar?

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top