IEnumerable vs List-무엇을 사용해야합니까? 그들은 어떻게 작동합니까?


677

열거 자 작동 방식과 LINQ에 대한 의구심이 있습니다. 다음 두 가지 간단한 선택을 고려하십시오.

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();

또는

IEnumerable<Animal> sel = (from animal in Animals 
                           join race in Species
                           on animal.SpeciesKey equals race.SpeciesKey
                           select animal).Distinct();

좀 더 일반적인 예처럼 보이도록 원래 객체의 이름을 변경했습니다. 쿼리 자체는 그렇게 중요하지 않습니다. 내가 묻고 싶은 것은 이것입니다 :

foreach (Animal animal in sel) { /*do stuff*/ }
  1. IE를 사용할 수있는 IEnumerable"sel"을 디버깅하고 검사 할 때 "inner", "outer", "innerKeySelector"및 "outerKeySelector"와 같은 흥미로운 멤버가있는 경우이 마지막 2 개가 나타납니다. 대의원이 될 수 있습니다. "inner"멤버에는 "Animal"인스턴스가 아니라 "Species"인스턴스가 있는데 이는 매우 이상했습니다. "outer"멤버는 "Animal"인스턴스를 포함합니다. 나는 두 대표가 어느 쪽이 들어오고 나가는지를 결정한다고 가정합니다.

  2. "Distinct"를 사용하면 "inner"에 6 개의 항목이 포함되지만 (2 개만 구분되므로 올바르지 않습니다) "outer"에 올바른 값이 포함되어 있습니다. 다시 말하지만, 위임 된 메소드가 이것을 결정하지만 IEnumerable에 대해 아는 것보다 조금 더 큽니다.

  3. 가장 중요한 것은 두 가지 옵션 중 성능면에서 가장 좋은 방법은 무엇입니까?

.ToList()? 를 통한 악 목록 변환

아니면 열거자를 직접 사용합니까?

가능하다면 IEnumerable의 사용법을 설명하는 약간의 링크를 던져주십시오.

답변:


739

IEnumerableList는 해당 동작의 구현입니다. 을 사용할 때 IEnumerable컴파일러는 나중에 작업을 연기하여 길을 따라 최적화 할 수있는 기회를 제공합니다. ToList ()를 사용하면 컴파일러가 결과를 즉시 수정하도록합니다.

LINQ 표현식을 "스태킹"할 때마다 IEnumerable동작을 지정하여 LINQ에 평가를 연기하고 프로그램을 최적화 할 수있는 기회를주기 때문에을 사용합니다. LINQ가 데이터베이스를 열거 할 때까지 SQL을 생성하여 데이터베이스를 쿼리하지 않는 방법을 기억하십니까? 이걸 고려하세요:

public IEnumerable<Animals> AllSpotted()
{
    return from a in Zoo.Animals
           where a.coat.HasSpots == true
           select a;
}

public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Felidae"
           select a;
}

public IEnumerable<Animals> Canine(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Canidae"
           select a;
}

이제 초기 샘플 ( "AllSpotted")과 일부 필터를 선택하는 방법이 있습니다. 이제 당신은 이것을 할 수 있습니다 :

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());

따라서 List over를 사용하는 것이 더 빠릅 IEnumerable니까? 쿼리가 두 번 이상 실행되지 않도록하려는 경우에만 해당됩니다. 그러나 전반적으로 더 낫습니까? 위에서 Leopards와 Hyenas는 각각 단일 SQL 쿼리 로 변환되며 데이터베이스는 관련 행만 반환합니다. 그러나에서 from을 반환 한 AllSpotted()경우 데이터베이스가 실제로 필요한 것보다 훨씬 많은 데이터를 반환 할 수 있고 클라이언트에서 필터링을 수행하는주기가 낭비되므로 실행 속도가 느려질 수 있습니다.

프로그램에서는 끝까지 쿼리를 목록으로 변환하는 것을 연기하는 것이 좋습니다. 따라서 Leopards와 Hyenas를 두 번 이상 열거하려면 다음과 같이하십시오.

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();

11
나는 그들이 조인의 양면을 참조한다고 생각합니다. "SELECT * FROM Animals JOIN Species ..."를 수행하면 조인의 내부 부분은 Animals이고 외부 부분은 Species입니다.
Chris Wenham

10
IEnumerable <T> vs IQueryable <T>에 대한 답변을 읽었을 때 비슷한 설명을 보았으므로 IEnumerable은 런타임에서 LINQ to Objects를 사용하여 컬렉션을 쿼리하도록 자동으로 실행합니다. 그래서 저는이 3 가지 유형을 혼동합니다. stackoverflow.com/questions/2876616/…
Bronek

4
@Bronek 당신이 연결 한 대답은 사실입니다. IEnumerable<T>첫 번째 부분 이후에는 LINQ-To-Objects가 될 것입니다. 반면에는 IQuertable<T>발견 된 고양이 만 풀다운하여 쿼리를 구체화 할 수 있습니다.
Nate

21
이 답변은 매우 오도됩니다! @Nate의 의견은 이유를 설명합니다. IEnumerable <T>를 사용하는 경우 필터는 클라이언트 측에서 무엇이든 발생합니다.
Hans

5
예 AllSpotted ()는 두 번 실행됩니다. 이 답변의 더 큰 문제는 다음과 같습니다. "위에서 Leopards 및 Hyenas는 각각 단일 SQL 쿼리로 변환되며 데이터베이스는 관련 행만 반환합니다." where 절이 IEnumerable <>에서 호출되고 데이터베이스에서 이미 온 개체를 반복하는 방법 만 알고 있기 때문에 이것은 거짓입니다. AllSpotted ()와 Feline () 및 Canine ()의 매개 변수를 IQueryable로 반환하면 SQL에서 필터가 발생하며이 답변이 의미가 있습니다.
Hans

178

Claudio Bernasconi의 TechBlog는 다음과 같이 작성했습니다. IEnumerable, ICollection, IList 및 List 사용시기

시나리오 및 기능에 대한 몇 가지 기본 사항은 다음과 같습니다.

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오


25
이 기사는 내부 작업이 아닌 코드의 공개 부분에만 해당된다는 점을 지적해야합니다. List의 구현 IList등은 이들의 상부에 추가의 기능을 갖는 한 IList(예를 들어 Sort, Find, InsertRange). 만약 당신이 자신을 IList지나치게 사용한다면 List, 당신은 당신이 요구할 수있는이 방법들을
잃어 버립니다

4
잊지 마세요IReadOnlyCollection<T>
Dandré

2
[]여기에 일반 배열을 포함시키는 것이 도움이 될 수 있습니다 .
jbyrd

눈살을 찌푸리는 동안이 그래픽과 기사를 공유해 주셔서 감사합니다.
Daniel

133

구현하는 클래스 IEnumerable를 사용하면 foreach구문 을 사용할 수 있습니다 .

기본적으로 컬렉션의 다음 항목을 가져 오는 방법이 있습니다. 전체 컬렉션이 메모리에있을 필요는 없으며 얼마나 많은 항목이 있는지 알지 foreach못하고 다음 항목이 계속 나올 때까지 계속 가져옵니다.

이것은 특정 상황에서 매우 유용 할 수 있습니다 (예 : 대규모 데이터베이스 테이블의 경우 행 처리를 시작하기 전에 전체 내용을 메모리에 복사하지 않으려는 경우).

이제는 List구현 IEnumerable하지만 메모리의 전체 컬렉션을 나타냅니다. 가 있고 IEnumerable호출 .ToList()하면 메모리에 열거 내용이 포함 된 새 목록을 만듭니다.

linq 표현식은 열거를 리턴하며 기본적으로을 사용하여 반복 할 때 표현식이 실행됩니다 foreach. IEnumerableLINQ 문이 실행 당신은을 반복 할 때 foreach,하지만 당신은 사용 빨리 반복을 강제 할 수 있습니다 .ToList().

여기 내가 의미하는 바가있다 :

var things = 
    from item in BigDatabaseCall()
    where ....
    select item;

// this will iterate through the entire linq statement:
int count = things.Count();

// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();

// this will execute the linq statement *again*
foreach( var thing in things ) ...

// this will copy the results to a list in memory
var list = things.ToList()

// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();

// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...

2
그러나 IEnumerable에서 foreach를 먼저 List로 변환하지 않고 실행하면 어떻게됩니까 ? 전체 컬렉션을 메모리에 가져 옵니까? 또는 foreach 루프를 반복하면서 요소를 하나씩 인스턴스화합니까? 감사합니다
Pap

@Pap 후자 : 다시 실행되고 아무것도 자동으로 메모리에 캐시되지 않습니다.
Keith

주요 차이점은 1) 메모리의 모든 것입니다. 2) IEnumerable을 사용 foreach하면 List가 index로 이동 하는 동안 사용할 수 있습니다 . 내가 알고 싶습니다 지금, 만약 카운트 / 길이thing사전을 IEnumerable을하지 도움이 될 것입니다, 맞죠?
Jeb50

@ Jeb50 정확 하 - 모두 ListArray구현 IEnumerable. IEnumerable메모리 모음과 한 번에 하나의 항목을 가져 오는 큰 모음 모두에서 작동하는 최저 공통 분모로 생각할 수 있습니다 . 당신이 전화 IEnumerable.Count()를 할 때 당신 은 빠른 .Length재산을 호출 하거나 전체 컬렉션을 겪고 있을 수 있습니다 -요점은 IEnumerable당신이 모르는 것입니다. 그게 문제가 될 수 있지만, 당신은 단지에려고하는 경우에 foreach당신은 걱정하지 마십시오 - 당신의 코드는 작동합니다 Array또는 DataReader동일.
Keith

1
@MFouadKajj 사용중인 스택을 모르지만 각 행마다 요청을하지는 않습니다. 서버는 쿼리를 실행하고 결과 집합의 시작점을 계산하지만 모든 것을 얻지는 못합니다. 작은 결과 집합의 경우 단일 여행 일 가능성이 높고 큰 결과 집합의 경우 결과에서 더 많은 행에 대한 요청을 보내지 만 전체 쿼리를 다시 실행하지는 않습니다.
Keith

97

아무도 이것과 중복되는 질문에 대해 아이러니하게 대답 한 한 가지 중요한 차이점을 언급하지 않았습니다.

IEnumerable은 읽기 전용이며 List는 아닙니다.

List와 IEnumerable의 실제 차이점 보기


후속 조치로서 인터페이스 측면 또는 목록 측면 때문입니까? 즉, IList도 읽기 전용입니까?
Jason Masters

IList는 읽기 전용이 아닙니다 -docs.microsoft.com/en-us/dotnet/api/… IEnumerable은 일단 구성되면 일단 추가하거나 제거 할 방법이 없기 때문에 읽기 전용입니다. 기본 인터페이스 중 하나입니다. IList 연장 (링크 참조)
CAD bloke

67

가장 중요한 것은 Linq를 사용하여 쿼리가 즉시 평가되지 않는다는 것입니다. 단지 결과를 통해 반복하는의 일환으로 실행되는 IEnumerable<T>A의 foreach-의 어떤 모든 이상한 대표를하고 있다는 것을.

따라서 첫 번째 예 ToList에서는 쿼리 결과를 호출 하고 목록에 넣어서 즉시 쿼리를 평가 합니다.
두 번째 예는 IEnumerable<T>나중에 쿼리를 실행하는 데 필요한 모든 정보가 포함 된를 반환합니다 .

성능면에서 그 대답은 에 달려 있습니다. 결과를 한 번에 평가해야하는 경우 (예 : 나중에 쿼리하는 구조를 변경하거나 IEnumerable<T>오랜 시간이 걸리는 반복을 원하지 않는 경우 ) 목록을 사용하십시오. 그렇지 않으면를 사용하십시오 IEnumerable<T>. 결과를 목록에 저장해야하는 특별한 이유가없는 한 일반적으로 적은 메모리를 사용하므로 두 번째 예에서는 주문형 평가를 기본값으로 사용해야합니다.


답변 해 주셔서 감사합니다. : :-). 이것은 거의 모든 의심을 해결했다. Enumerable이 "내부"와 "외부"로 "분할"된 이유가 있습니까? 이것은 마우스를 통해 디버그 / 브레이크 모드에서 요소를 검사 할 때 발생합니다. 이것은 아마도 Visual Studio의 기여입니까? 그 자리에서 열거하고 Enum의 입력 및 출력을 표시합니까?
Axonn

5
즉,이의 Join조인의 내부와 외부 두 측면이다 - 그것의 일을. 일반적으로 IEnumerables의 실제 코드는 실제 코드와 완전히 다르므로 걱정하지 마십시오 . 반복 할 때 실제 출력에 대해서만 걱정하십시오 :)
thecoop

40

IEnumerable의 장점은 지연된 실행 (일반적으로 데이터베이스 사용)입니다. 실제로 데이터를 반복 할 때까지 쿼리가 실행되지 않습니다. 필요할 때까지 기다리는 쿼리입니다 (일명 지연 로딩).

ToList를 호출하면 쿼리가 실행되거나 "구체적으로 구체화"됩니다.

둘 다 장단점이 있습니다. ToList를 호출하면 쿼리가 실행될 때의 미스터리를 제거 할 수 있습니다. IEnumerable을 고수하면 실제로 필요할 때까지 프로그램이 작동하지 않는다는 이점이 있습니다.


25

나는 하루에 빠진 잘못 사용 된 개념 하나를 공유 할 것이다.

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));


// updating existing list
names[0] = "ford";

// Guess what should be printed before continuing
print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

예상 결과

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

실제 결과

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

설명

다른 답변에 따라 결과 평가는 ToList예를 들어 호출 또는 유사한 호출 방법 까지 연기 ToArray됩니다.

따라서이 경우 코드를 다음과 같이 다시 작성할 수 있습니다.

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

// updating existing list
names[0] = "ford";

// before calling ToList directly
var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));

print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

아케이드 플레이

https://repl.it/E8Ki/0


1
이것은 linq 메소드 (확장자) 때문입니다.이 경우 쿼리를 생성하지만 실행하지 않는 IEnumerable에서 제공됩니다 (표현 트리가 사용되는 장면 뒤). 이렇게하면 데이터 (이 경우 목록의 데이터)를 건드리지 않고 해당 쿼리로 많은 작업을 수행 할 수 있습니다. List 메소드는 준비된 쿼리를 가져 와서 데이터 소스에 대해 실행합니다.
Bronek

2
실제로, 나는 모든 답변을 읽었으며, 귀하의 답변은 내가 VOQ에 대한 답변이었습니다. 왜냐하면 LINQ / SQL에 대해 구체적으로 이야기하지 않고 둘 사이의 차이점을 분명히 밝히기 때문입니다. LINQ / SQL에 도달하기 전에이 모든 것을 알아야합니다. 존경 해
BeemerGuy

그것은 설명하는 중요한 차이점이지만 "예상 결과"는 실제로 예상되지 않습니다. 당신은 그것이 디자인이 아니라 어떤 종류의 차인 것처럼 말하고 있습니다.
Neme

@Neme, 그렇습니다. 어떻게 IEnumerable작동 하는지 이해하기 전에 기대했던 것이었지만 이제는 어떻게하는지 알기 때문에 더 이상은 아닙니다.)
amd

15

원하는 모든 항목을 열거하려면를 사용하십시오 IEnumerable.

그러나 열거되는 원본 컬렉션을 변경하는 것은 위험한 작업이므로이 작업을 ToList먼저 수행해야합니다. 이렇게하면 메모리의 각 요소에 대해 새 목록 요소가 만들어지고 열거 IEnumerable되므로 한 번만 열거하면 성능이 떨어집니다.하지만 더 안전하고 때로는 List메소드가 편리합니다 (예 : 임의 액세스).


1
목록을 생성하면 성능이 저하된다고 말하는 것이 안전하지 않습니다.
Steven Sudit

@ Steven : 실제로 thecoop과 Chris가 말했듯이 때로는 List를 사용해야 할 수도 있습니다. 제 경우에는 그렇지 않다는 결론을 내 렸습니다. @ Daren : "이것은 메모리의 각 요소에 대한 새로운 목록을 만들 것입니다"는 무엇을 의미합니까? "목록 항목"을 의미했을까요? ::-).
Axonn

@Axonn 예, 목록 항목을 작성했습니다. 결정된.
Daren Thomas

@Steven의 요소를 반복하려는 경우 IEnumerable먼저 목록을 작성하고 반복하면 요소를 두 번 반복한다는 의미 입니다. 따라서 목록에서보다 효율적인 작업을 수행하지 않으려면 실제로 성능이 저하됩니다.
Daren Thomas

3
@jerhewet : 반복되는 시퀀스를 수정하는 것은 좋은 생각이 아닙니다. 나쁜 일이 일어날 것입니다. 추상화가 누출됩니다. 악마는 우리 차원에 침입하여 혼란을 일으킬 것입니다. 그래서 그렇습니다, .ToList()여기 에 도움이됩니다)
Daren Thomas

5

위에 게시 된 모든 답변 외에도 여기에 2 센트가 있습니다. IEnumerable과 같은 ICollection, ArrayList 등을 구현하는 List 이외의 많은 유형이 있습니다. 따라서 IEnumerable을 메서드의 매개 변수로 사용하는 경우 컬렉션 유형을 함수에 전달할 수 있습니다. 즉, 특정 구현이 아닌 추상화에서 작동하는 방법을 가질 수 있습니다.


1

IEnumerable을 List로 변환 할 수없는 경우 (무한 목록 또는 매우 큰 목록 등)가 많이 있습니다. 가장 명백한 예는 모든 소수, 세부 정보가 포함 된 모든 페이스 북 사용자 또는 이베이의 모든 항목입니다.

차이점은 "목록"개체는 "지금 바로 여기에"저장되는 반면 "IEnumerable"개체는 "한 번에 하나씩"작동한다는 것입니다. 따라서 ebay의 모든 항목을 살펴보면 한 번에 하나씩 작은 컴퓨터에서도 처리 할 수 ​​있지만 ".ToList ()"는 컴퓨터의 크기에 상관없이 메모리가 부족할 수 있습니다. 어떤 컴퓨터도 그렇게 많은 양의 데이터를 포함하고 처리 할 수는 없습니다.

[편집]-말할 것도없이 –“이것도 저것도”가 아닙니다. 종종 같은 클래스에서 목록과 IEnumerable을 모두 사용하는 것이 좋습니다. 세계적으로 어떤 소수의 메모리도 필요하기 때문에 모든 소수를 나열 할 수있는 컴퓨터는 없습니다. 하지만 당신은 쉽게 생각할 수있는 class PrimeContainer을 포함 IEnumerable<long> primes분명한 이유도를 포함하는, SortedList<long> _primes. 지금까지 계산 된 모든 소수. 검사 할 다음 프라임은 기존 프라임 (제곱근까지)에 대해서만 실행됩니다. 그렇게하면 한 번에 하나씩 소수 (IEnumerable)와 "지금까지 프라임"의 좋은 목록을 얻을 수 있습니다. 이는 전체 (무한) 목록의 근사치입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.