문제

다음 코드의 ContainsIngredients 메서드에서 p.성분 명시적으로 여러 번 참조하는 대신 값을 사용하시겠습니까?이것은 설명을 목적으로 방금 만든 아주 사소한 예이지만, 제가 작업 중인 코드는 내부 깊은 곳에서 값을 참조합니다. 예. 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 호출의 결과를 보다 복잡한 부울 표현식으로 결합할 수 있습니다(각 호출마다 비용이 많이 드는 적절한 값을 캐싱). 또는 복잡한 심층 계층을 처리하기 위해 두 번째 매개변수로 전달된 람다 내에 중첩할 수 있습니다.

하지만 코드를 읽는 것은 상당히 어려울 것이며 모든 TestWith 호출에 더 많은 스택 전환을 도입할 수 있으므로 성능 향상 여부는 ExpensiveCall()의 비용이 얼마나 비싼지에 따라 달라집니다.

참고로, 내가 아는 한 식 컴파일러는 해당 수준의 최적화를 수행하지 않기 때문에 다른 답변에서 제안한 것처럼 원래 예제에는 인라인이 없습니다.

이 경우에는 아니오라고 대답하겠습니다.나는 컴파일러가 다음을 사용한다는 것을 알아낼 수 있다고 가정합니다. p.Ingredients 변수를 3번 호출하고 변수를 스택이나 레지스터 또는 사용하는 모든 항목에 가깝게 유지합니다.

Turbulent Intellect에는 정확한 답이 있습니다.

사용 중인 유형에서 일부 null 및 예외를 제거하여 사용하기 더 친숙하게 만들 수 있다는 점을 조언하고 싶습니다.

    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, 왜 읽기 전용인가요?

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top