728x90
반응형
1. 핵심 정리
using System;
using System.Collections;
using System.Collections.Generic;
class Node {
public int data;
public Node next;
public Node(int d, Node n) { data = d; next = n; }
}
// 열거자
class IntLinkedListEnumerator : IEnumerator {
public Node head = null;
public Node current = null;
public IntLinkedListEnumerator(Node n) { head = n; }
public object Current => current.data;
public bool MoveNext() {
if (current == null)
current = head;
else
current = current.next;
return current != null;
}
public void Reset() {
current = null;
}
}
class IntLinkedList : IEnumerable {
public Node head = null;
public void AddFirst(int data) {
head = new Node(data, head);
}
public IEnumerator GetEnumerator() {
return new IntLinkedListEnumerator(head);
}
}
class Program {
static void Main() {
IntLinkedList st = new IntLinkedList();
st.AddFirst(10);
st.AddFirst(20);
st.AddFirst(30);
st.AddFirst(40);
st.AddFirst(50);
IEnumerator e = st.GetEnumerator();
While(e.MoveNext()) {
Console.WriteLine(e.Current);
}
}
}
저번 글에서 만든 코드이다.
C#에서 컬렉션은 열거자를 반환할 수 있어야 하고 그에 맞는 열거자 타입을 설계해야 한다.
하지만 코루틴을 사용하면 훨씬 간단하게 열거자를 만들 수 있다.
① 모든 컬렉션은 열거자를 반환할 수 있어야 한다.
=> 컬렉션 자신만의 열거자 타입을 설계해야 한다.
② COROUTINE을 사용한 열거자 만들기
=> 별도의 열거자 타입이 필요 없다.
=> GetEnumerator() 메소드에서 컬렉션이 가진 모든 요소에 대해 yield return 수행
using System;
using System.Collections;
using System.Collections.Generic;
class Node {
public int data;
public Node next;
public Node(int d, Node n) { data = d; next = n; }
}
class IntLinkedList : IEnumerable {
public Node head = null;
public void AddFirst(int data) {
head = new Node(data, head);
}
public IEnumerator GetEnumerator() { // 메소드 타입과 리턴 타입 다름
// return new 열거자반환;
Node current = head;
while(current != null) {
return current.data;
current = current.next; // 실행되지 않음
}
}
}
class Program {
static void Main() {
IntLinkedList st = new IntLinkedList();
st.AddFirst(10);
st.AddFirst(20);
st.AddFirst(30);
st.AddFirst(40);
st.AddFirst(50);
IEnumerator e = st.GetEnumerator();
While(e.MoveNext()) {
Console.WriteLine(e.Current);
}
}
}
저번글에서 만든 열거자를 지우고 GetEnumerator메소드에서 직접 만들어본다.
현재 위의 코드의 형식이 되었는데 몇 가지 문제가 보인다.
1. 메소드 타입과 리턴 타입이 다르다. (IEnumerator와 int)
2. while문에서 루프를 돌아도 바로 return문을 만나므로 1번만 실행되고, 26번 라인은 실행되지 않는다.
하지만 return 앞에 yield를 붙여주면 모든 문제가 사라진다.
using System;
using System.Collections;
using System.Collections.Generic;
class Node {
public int data;
public Node next;
public Node(int d, Node n) { data = d; next = n; }
}
class IntLinkedList : IEnumerable {
public Node head = null;
public void AddFirst(int data) {
head = new Node(data, head);
}
public IEnumerator GetEnumerator() {
// return new 열거자반환;
Node current = head;
while(current != null) {
yield return current.data;
current = current.next;
}
}
}
class Program {
static void Main() {
IntLinkedList st = new IntLinkedList();
st.AddFirst(10);
st.AddFirst(20);
st.AddFirst(30);
st.AddFirst(40);
st.AddFirst(50);
IEnumerator e = st.GetEnumerator();
While(e.MoveNext()) {
Console.WriteLine(e.Current);
}
}
}
2. 동작 원리
using System;
using System.Collections;
using System.Collections.Generic;
class Node {
public int data;
public Node next;
public Node(int d, Node n) { data = d; next = n; }
}
class IntLinkedList : IEnumerable {
public Node head = null;
public void AddFirst(int data) {
head = new Node(data, head);
}
public IEnumerator GetEnumerator() {
Node current = head;
while(current != null) {
yield return current.data;
current = current.next;
}
}
}
class Program {
static void Main() {
IntLinkedList st = new IntLinkedList();
st.AddFirst(10);
st.AddFirst(20);
st.AddFirst(30);
st.AddFirst(40);
st.AddFirst(50);
IEnumerator e = st.GetEnumerator();
While(e.MoveNext()) {
Console.WriteLine(e.Current);
}
}
}
위 코드의 동작원리를 그림으로 보면 아래와 같다.
먼저 특이한 점은 일반 메소드와 다르게 GetEnumerator();를 호출해서 메소드가 시작되지 않는다.
MoveNext()를 불렀을 때 처음으로 메소드가 시작된다.
C#이 열거자를 만들때만 이 기법을 사용할 수 있다.
그래서 MoveNext일 때 부르도록 약속을 다 한 것이다.
첫 MoveNext를 호출하면 메소드가 시작되고
data는 그 다음 Current를 호출했을 때 들어간다.
그리고 다시 MoveNext를 호출하면 처음부터가 아닌 다음을 부르게 되고
while문에서 current가 null이 아니므로 다시 data를 Current를 호출했을 때 부르게 된다.
current가 null이 아니면 밑에서 MoveNext를 하면 계속 반복하게 된다.
C#의 코루틴이 모든 종류를 이렇게 만들 수는 없고 열거자에서만 이렇게 만들 수 있다.
로깅을 위한 코드를 추가하고 실행해보면 알 수 있다.
밑에서 확인해보자.
using System;
using System.Collections;
using System.Collections.Generic;
class Node {
public int data;
public Node next;
public Node(int d, Node n) { data = d; next = n; }
}
class IntLinkedList : IEnumerable {
public Node head = null;
public void AddFirst(int data) {
head = new Node(data, head);
}
public IEnumerator GetEnumerator() {
Console.WriteLine("GetEnumerator() 시작");
Node current = head;
while(current != null) {
Console.WriteLine("yield return");
yield return current.data;
current = current.next;
}
}
}
class Program {
static void Main() {
IntLinkedList st = new IntLinkedList();
st.AddFirst(10);
st.AddFirst(20);
st.AddFirst(30);
st.AddFirst(40);
st.AddFirst(50);
IEnumerator e = st.GetEnumerator();
Console.WriteLine("GetEnumerator() 호출");
While(e.MoveNext()) {
Console.WriteLine("MoveNext() 호출");
Console.WriteLine(e.Current);
}
}
}
원래 보통의 메소드대로라면 실행결과가 아래와 같이 나와야 정상일 것이다.
실행 결과
GetEnumerator() 시작
yield return
GetEnumerator() 호출
MoveNext() 호출
50
yield return
MoveNext() 호출
40
...
하지만 실제 실행 결과를 보면 아래와 같다.
실행 결과
GetEnumerator() 호출
GetEnumerator() 시작
yield return
MoveNext() 호출
50
yield return
MoveNext() 호출
40
...
728x90
반응형
'프로그래밍 > C#' 카테고리의 다른 글
[C#] LINQ 개념 (Language INtegrated Query) (0) | 2020.05.06 |
---|---|
[C#] 코루틴 (COROUTINE) (3) (0) | 2020.05.05 |
[C#] 코루틴 (COROUTINE) (1) (0) | 2020.04.29 |
[C#] Collection Method (0) | 2020.04.27 |
[C#] 열거자 (Enumerator) (0) | 2020.04.26 |