هل من الممكن تخزين قيمة تم تقييمها مؤقتًا في تعبير لامدا؟

StackOverflow https://stackoverflow.com/questions/66382

  •  09-06-2019
  •  | 
  •  

سؤال

في الطريقة التي تحتوي على المكونات في الكود التالي، هل من الممكن تخزين ملف ص.المكونات القيمة بدلاً من الإشارة إليها بشكل صريح عدة مرات؟هذا مثال تافه إلى حد ما قمت بإعداده للتو لأغراض توضيحية، لكن الكود الذي أعمل عليه يشير إلى قيم عميقة في الداخل ص على سبيل المثال. p.InnerObject.ExpensiveMethod().Value

يحرر:أنا أستخدم PredicateBuilder من 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);
    }
}
هل كانت مفيدة؟

المحلول

ألا يمكنك ببساطة كتابة تعبيرك المنطقي في وظيفة ثابتة منفصلة تستدعيها من لامدا - مع تمرير 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;
}

نصائح أخرى

هل فكرت الحفظ?

الفكرة الأساسية هي هذه؛إذا كان لديك استدعاء دالة باهظ الثمن، فهناك دالة ستحسب القيمة الباهظة عند الاستدعاء الأول، ولكنها سترجع نسخة مخبأة بعد ذلك.تبدو الوظيفة هكذا؛

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 المتعددة معًا في تعبير منطقي أكثر تعقيدًا عند الحاجة - تخزين القيمة المكلفة المناسبة مؤقتًا مع كل مكالمة - أو يمكنك دمجها داخل lambdas التي تم تمريرها كمعلمة ثانية للتعامل مع التسلسلات الهرمية العميقة المعقدة.

سيكون من الصعب جدًا قراءة التعليمات البرمجية، وبما أنك قد تقدم مجموعة من التحولات المكدسة مع جميع مكالمات TestWith، فإن تحسين الأداء سيعتمد على مدى تكلفة ExpensiveCall() الخاص بك.

كملاحظة، لن يكون هناك أي تضمين في المثال الأصلي كما هو مقترح في إجابة أخرى نظرًا لأن مترجم التعبير لا يقوم بهذا المستوى من التحسين على حد علمي.

أود أن أقول لا في هذه الحالة.أفترض أن المترجم يمكنه معرفة أنه يستخدم ملف p.Ingredients المتغير 3 مرات وسيبقي المتغير قريبًا على المكدس أو السجلات أو أي شيء يستخدمه.

الذكاء المضطرب لديه الإجابة الصحيحة تمامًا.

أريد فقط أن أنصحك بأنه يمكنك إزالة بعض القيم الخالية والاستثناءات من الأنواع التي تستخدمها لجعل استخدامها أكثر سهولة.

    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;

ملاحظة: لماذا للقراءة فقط؟

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top