[37] 쿼리를 사용할 때는 즉시 평가보다 지연 평가가 낫다

다음 본문은 도서 이펙티브 C# (빌 와그너)에서 나오는 주제를 다룹니다.

 

 

지연평가

쿼리를 정의하더라도 결과 데이터나 시퀀스를 즉각 얻어 오는 것은 아니다.

쿼리 정의는 작업 절차를 정의한 것일 뿐이다.

실제로 쿼리의 결과를 이용하여 순회를 수행해야만 결과가 생성된다.

 

다음은 쿼리의 결과를 대상으로 추가적인 쿼리를 수행하는 코드이다.

var sequence1 = Getnerate(10, () => DateTime.Now);
var sequence2 = from value in sequence1
                select value.ToUniversalTime();

sequence2를 순회할 때 sequence1이 이미 생성해둔 값을 순회하면서 개별 요소를 수정하는 것이 아니라,

순회 시점에 맞춰 sequence1이 값을 생성한다.

 

하지만 일부 쿼리 표현식은 결과를 얻기 위해서 반드시 전체 시퀀스가 필요한 경우가 있다.

where, orderby, Max, Min은 전체 시퀀스를 요구한다.

 

다음의 식은 영원히 수행(혹은 int.MaxValue까지) 수행된다.

쿼리 구문을 수행하기 위해서 시퀀스 내의 모든 값을 대상으로 비교 연산을 수행하기 때문이다.

static void Main(string[] args)
{
    var answers = from number in AllNumbers()
                    where number < 10
                    select number;
                    
    foreach(var num in small answers)
        Console.WriteLine(num);
}

static IEnumerable<int> AllNumbers()
{
    var number = 0;
    while(number < int.MaxValue)
    {
        yield return number++;
    }
}

 

이를 해결하기위해 다음과 같이 고쳐 써볼 수 있다.

Take()는 시퀀스로부터 처음 N개의 객체를 반환한다.

static void Main(string[] args)
{
    var answers = from number in AllNumbers()
                    select number;
                    
    var smallNumbers = answers.Take(10));
    foreach(var num in small Numbers)
        Console.WriteLine(num);
}

static IEnumerable<int> AllNumbers()
{
    var number = 0;
    while(number < int.MaxValue)
    {
        yield return number++;
    }
}

 

전체 시퀀스를 사용하는 메서드를 사용할 때 주의사항

1. 시퀀스가 무한정 지속될 가능성이 있다면 이 같은 메서드를 사용할 수 없다.
2. 시퀀스가 무한이 아니더라도 필터링은 다른 쿼리보다 먼저 수행하는 것이 좋다.
   개별 요소의 개수가 줄어 다음으로 수행할 쿼리의 성능을 개선할 수 있기 때문이다.
// 정렬 후 필터링
var sortedProjuctsSlow = from p in products
                         orderby p.UnitsInStock descending
                         where p.UnitsInStock > 100
                         select p;
                         
// 필터링 후 정렬
var sortedProjuctsSlow = from p in products
                         where p.UnitsInStock > 100
                         orderby p.UnitsInStock descending
                         select p;

 

결론

 거의 대부분의 경우 지연평가는 성능과 유연성에서 이득을 볼 수 있다.

 즉각적으로 쿼리의 결과를 가져와야한다면 ToList()나 ToArray()를 사용하자.

 

 

댓글

Designed by JB FACTORY