728x90
반응형
1. 문제
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
for(int i=0; i < c1.Count; i++) {
Console.WriteLine(c1[i]);
}
}
}
//-----------------------------------------------------
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
LinkedList<int> c1 = new LinkedList<int>(arr);
for(int i=0; i < c1.Count; i++) {
Console.WriteLine(c1[i]); // error
}
}
}
List를 배열로 초기화하고 요소를 출력해보는 간단한 코드이다.
위에서 List는 연속된 메모리에 요소가 놓이게 되는게 메모리 공간을 떨어뜨려 놓고 싶어졌다고 해보자.
이럴 땐 LinkedList라는게 있어 이걸 사용하면 요소별로 메모리가 따로 떨어지게 된다.
하지만 문제는 c1[i]와 같이 인덱서를 제공하지 않아 출력을 못한다는 문제가 있다.
인덱서를 제공하고 List -> LinkedList만 바꾸면 된다면
바꿔가면서 성능을 비교해보면 좋을텐데 안된다는게 문제이다.
이 때, 반복자 패턴이란 것이 있다.
① Collection과 인덱서(indexer)
=> IList<T> 인터페이스를 구현한 컬렉션은 인덱서를 제공
=> LinkedList<T>는 인덱서를 제공 안함
② 반복자(iterator) 패턴
=> 복합객체의 내부 구조에 상관 없이 동일한 방식의 요소를 열거하는 디자인 패턴
=> Collection 의 내부 구조에 상관없이 동일한 방법으로 요소를 열거
=> C#에서는 반복자(iterator) 대신 열거자(enumerator)라는 용어를 사용
2. 해결
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
LinkedList<int> c2 = new LinkedList<int>(arr);
}
}
C#에서 모든 컬렉션은 IEnumerable<T> 인터페이스를 구현한다.
이 얘기는 공통의 어떠한 메소드가 있을 것이란 이야기다.
이 메소드는 GetEnumerator()이다.
메소드가 반환하는 것을 열거자라고 부르는데 이것은 객체이다.
객체가 요소를 가리키며 다음 요소로 이동하거나 값을 꺼내올 수 있게 된다.
또, 모든 열거자의 사용법은 동일하다.
왜냐하면 IEnumerator<T> 인터페이스를 구현하기 때문이다.
여기서 인터페이스 이름을 헷갈릴 수 있는데 IEnumerable<T>은 컬렉션의 인터페이스이고
IEnumerator<T>는 열거자의 인터페이스이다.
따라서 반환값을 받을 때 IEnumerator<T>로 받으면 된다.
요즘은 var를 많이 사용한다. (여기서는 IEnumerator<T>를 사용한다.)
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
LinkedList<int> c2 = new LinkedList<int>(arr);
IEnumerator<int> e1 = c1.GetEnumerator();
IEnumerator<int> e2 = c2.GetEnumerator();
// var e1 = c1.GetEnumerator();
}
}
위와 같이 IEnumerator객체를 만들면 된다.
이게 객체를 만들었으니 초기화를 하고, 요소를 꺼내볼 수 있다.
아래에서 초기화하고 요소를 꺼내오는 코드를 보자.
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
LinkedList<int> c2 = new LinkedList<int>(arr);
IEnumerator<int> e1 = c1.GetEnumerator();
IEnumerator<int> e2 = c2.GetEnumerator();
// var e1 = c1.GetEnumerator();
e1.MoveNext(); // 최초 호출 - 초기화
Console.WriteLine(e1.Current); // 첫 번째 요소 꺼내오기 - 1
e1.MoveNext(); // 다음으로 이동
Console.WriteLine(e1.Current); // 두 번째 요소 꺼내오기 - 2
}
}
위와 같이 MoveNext()로 다음 요소로 이동 한 후 Current로 요소를 꺼내올 수 있다.
5개의 요소를 꺼내려면 5번을 해야하는데 딱 봐도 비효율 적이다.
그렇다면 모든 요소를 꺼내려면 어떤 방법이 있을까?
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
LinkedList<int> c2 = new LinkedList<int>(arr);
IEnumerator<int> e1 = c1.GetEnumerator();
IEnumerator<int> e2 = c2.GetEnumerator();
// var e1 = c1.GetEnumerator();
while(e1.MoveNext()) // 더 이상 Next못 할 시 false 반환
Console.WriteLine(e1.Current);
while(e2.MoveNext())
Console.WriteLine(e2.Current);
e1.Reset();
While(e1.MoveNext())
Console.WriteLine(e1.Current);
}
}
위와 같이 메모리가 연속되어 있는 List도, 떨어져있는 LinkedList도 같은 방법으로 사용 가능하다.
다시 처음 위치로 보내는 초기화는 Reset()이 있고 위와 같이 또 쓸 수 있다.
① 모든 컬렉션은 IEnumerable<T> 인터페이스를 구현한다.
=> 열거자를 꺼내는 GetEnumerator() 라는 메소드를 제공한다.
② 열거자
=> 컬렉션의 요소를 가리키는 객체
=> MoveNext(), Current, Reset() 멤버로 모든 요소에 접근 가능
=> 모든 열거자는 사용법이 동일하다.
=> 모든 열거자는 IEnumerator<T> 인터페이스를 구현하고 있다.
3. foreach 원리
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
foreach(int n in c1) {
Console.WriteLine(n);
}
}
}
모두 알다시피 foreach문이 있어 모든 컬렉션의 요소를 편하게 뽑아볼 수 있다.
그런데 왜 위와같이 IEnumerator로 받아서 열거하는 방법을 설명했을까?
사실 foreach문을 사용하면 내부적으로 열거자를 사용하는 코드로 바뀌게 된다.
컴파일러가 바꾸는 코드는 아래와 같다.
열거자 개념을 통해 foreach가 동작하는 원리를 알아두면 되겠다.
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
foreach(int n in c1) {
Console.WriteLine(n);
}
for(IEnumerator<int> p = c1.GetEnumerator(); p.MoveNext(); ) {
int n = p.Current;
Console.WriteLine(n);
}
}
}
① 모든 컬렉션은 foreach를 사용해서 열거 할 수 있다.
728x90
반응형
'프로그래밍 > C#' 카테고리의 다른 글
[C#] 코루틴 (COROUTINE) (1) (0) | 2020.04.29 |
---|---|
[C#] Collection Method (0) | 2020.04.27 |
[C#] Collection과 Interface (0) | 2020.04.26 |
[C#] 컬렉션 (Collection) (0) | 2020.04.23 |
[C#] try ~ finally (0) | 2020.04.21 |