where 조건과 continue 가드 절을 사용하여 foreach 필터링


24

일부 프로그래머가 이것을 사용하는 것을 보았습니다.

foreach (var item in items)
{
    if (item.Field != null)
        continue;

    if (item.State != ItemStates.Deleted)
        continue;

    // code
}

내가 일반적으로 사용하는 대신

foreach (var item in items.Where(i => i.Field != null && i.State != ItemStates.Deleted))
{
    // code
}

나는 둘 다의 조합을 보았습니다. 나는 특히 더 복잡한 조건에서 '계속'으로 가독성을 좋아합니다. 성능에 차이가 있습니까? 데이터베이스 쿼리를 사용한다고 가정합니다. 일반 목록은 어떻습니까?


3
일반 목록의 경우 마이크로 최적화처럼 들립니다.
묵시

2
@zgnilec : ...하지만 실제로 두 변형 중 어느 것이 최적화 된 버전입니까? 물론 그것에 대해 의견이 있지만 코드를 보는 것만으로는 모든 사람에게 명확하지 않습니다.
Doc Brown

2
물론 계속 더 빠를 것입니다. linq 사용. 추가 반복자를 작성하는 위치.
세상의 종말

1
@zgnilec-좋은 이론. 그렇게 생각 하는지 설명하는 답변을 게시 하시겠습니까? 현재 존재하는 두 대답은 그 반대입니다.
Bobson

2
결론은 다음과 같습니다. 두 구문 사이의 성능 차이는 무시할 수 있으며, 가독성과 디버깅 가능성을 모두 달성 할 수 있습니다. 그것은 당신이 선호하는 맛의 문제입니다.
Doc Brown

답변:


63

나는 이것을 명령 / 쿼리 분리 를 사용하기에 적절한 장소로 간주합니다 . 예를 들면 다음과 같습니다.

// query
var validItems = items.Where(i => i.Field != null && i.State != ItemStates.Deleted);
// command
foreach (var item in validItems) {
    // do stuff
}

또한 쿼리 결과에 자체 문서화 이름을 지정할 수 있습니다. 또한 데이터를 쿼리하거나 수정하는 코드를 리팩토링 할 기회를 찾는 데 도움이됩니다. 두 가지를 모두 시도하는 혼합 코드보다 데이터 만 쿼리하거나 변경하는 코드가 훨씬 쉽습니다.

디버깅 할 때 해결 foreach내용 validItems이 예상 한대로 신속하게 확인 되기 전에 중단 할 수 있습니다 . 필요한 경우가 아니면 람다에 들어 가지 않아도됩니다. 람다로 들어가야하는 경우 별도의 함수로 분해하여 제안하는 것이 좋습니다.

성능에 차이가 있습니까? 쿼리는 데이터베이스에 의해 뒷받침된다면, LINQ 버전은이 잠재적 인 SQL 쿼리가 있기 때문에, 더 빠르게 실행할 수 있습니다 더 효율적입니다. LINQ to Objects 인 경우 실제 성능 차이가 나타나지 않습니다. 항상 그렇듯이 최적화를 미리 예측하지 않고 코드를 프로파일 링하고 실제로보고되는 병목 현상을 해결하십시오.


1
매우 큰 데이터 세트가 왜 차이를 만들까요? 람다의 소액의 비용이 결국 합산되기 때문에?
BlueRaja-대니 Pflughoeft

1
@ BlueRaja-DannyPflughoeft : 예, 맞습니다.이 예제에는 원본 코드 이외의 추가적인 알고리즘 복잡성이 포함되어 있지 않습니다. 문구를 제거했습니다.
Christian Hayter

컬렉션에 대해 두 번의 반복이 발생하지 않습니까? 당연히 두 번째 항목은 유효한 항목 만 포함한다는 것을 고려할 때 더 짧지 만 요소를 필터링하려면 두 번 수행해야하며 두 번째로 유효한 항목으로 작업해야합니다.
Andy

1
@DavidPacker 아니오 . 루프에서만 IEnumerable구동 foreach됩니다.
Benjamin Hodgson

2
@DavidPacker : 그것이 정확히하는 일입니다. 대부분의 LINQ to Objects 메서드는 반복자 블록을 사용하여 구현됩니다. 위의 예제 코드는 Where람다와 루프 본문 (람다가 true를 반환하는 경우)을 요소 당 한 번씩 실행하여 컬렉션을 정확히 한 번 반복 합니다.
Christian Hayter

7

물론 성능에 차이가 있으므로 .Where()모든 단일 항목에 대해 델리게이트 호출이 발생합니다. 그러나 성능에 대해 전혀 걱정하지 않습니다.

  • 델리게이트 호출에 사용 된 클럭 사이클은 컬렉션을 반복하고 조건을 확인하는 나머지 코드에서 사용하는 클럭 사이클과 비교하여 무시할 수 있습니다.

  • 델리게이트를 호출 할 때의 성능 저하는 몇 번의 클럭주기 정도이며, 운 좋게도 개별 클럭주기에 대해 걱정해야했던 시절이 지났습니다.

어떤 이유로 클럭 사이클 레벨에서 성능이 실제로 중요한 경우 List<Item>대신 대신 을 사용 IList<Item>하여 컴파일러가 가상 호출 대신 직접 (및 inlinable) 호출을 사용할 수 있고 List<T>실제로 반복자 가 a struct, 박스에 넣을 필요는 없습니다. 그러나 그것은 정말 사소한 일입니다.

데이터베이스 쿼리는 최소한 이론 상으로는 필터를 RDBMS로 보낼 가능성이 있기 때문에 성능이 크게 향상되기 때문에 다른 상황입니다. 일치하는 행만 RDBMS에서 프로그램으로 이동합니다. 그러나 나는 linq를 사용해야한다고 생각하기 때문에이 표현을 그대로 RDBMS에 보낼 수 있다고 생각하지 않습니다.

if(x) continue;이 코드를 디버깅해야하는 순간 의 이점을 실제로 알 수 있습니다. if()s와 continues에 대한 단일 스테핑 은 훌륭하게 작동합니다. 필터링 델리게이트를 한 단계 씩 실행하는 것은 고통스러운 일입니다.


즉, 무언가 잘못되었을 때 모든 항목을보고 디버거에서 어떤 필드가 Field! = null인지, 어느 것이 State! = null인지 확인하려고합니다. foreach로는 불가능하기 어려울 수 있습니다 ... 어디.
gnasher729

디버깅에 대한 좋은 지적. Visual Studio에서 where를 사용하는 것은 그리 나쁘지 않지만 다시 컴파일하지 않고 디버깅하는 동안 람다 식을 다시 작성할 수 없으므로를 사용할 때 피하십시오 if(x) continue;.
Paprik

엄밀히 말하면 .Where한 번만 호출됩니다. 무엇 반복 될 때마다 호출됩니다하면 필터 위임 (그리고 MoveNext그리고 Current그들이 밖으로 최적화되지 않는 열거에)
CodesInChaos

@CodesInChaos 그것은 당신이 무슨 말을하는지 이해하는 데 약간의 생각이 들었지만 물론 wh00ps, 당신은 옳고 엄격하게 말하면 .Where한 번만 호출됩니다. 고쳤다.
Mike Nakis
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.