728x90
반응형
1. 핵심 정리
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.Select(n => n * 10);
arr[0] = 0;
foreach(int n in e)
Console.WriteLine(n);
}
}
1부터 5까지 5개의 요소가 들어있는 배열이 있다.
그리고 query method를 이용하여 요소마다 * 10을 한 값을 출력하려고 한다.
이 때, 배열의 0번째 요소에 0을 넣고 출력한다면 결과가 어떻게 나올까?
어차피 열거자로 뽑은 이후에 0을 넣은것이니 그대로 10, 20, 30, 40, 50 이 출력될까?
0, 20, 30, 40, 50로 출력될까?
실행해보면 0, 20, 30, 40, 50으로 출력된다.
9번 라인에서 요소에 값이 적용된게 아닌, 요소를 꺼낼 때 적용되기 때문이다.
이러한 것을 지연된 실행이라고 한다.
① 지연된 실행
=> query method를 호출하는 시점이 foreach 문에서 요소에 접근하는 시점에 연산이 적용되는 것
2. Select 구현
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.MySelect(n => n * 10);
foreach(int n in e)
Console.WriteLine(n);
}
}
이제 Select를 MySelect로 바꾸고 구현해보도록 한다.
앞서, 해당 내용을 이해하려면 아래의 내용을 먼저 이해해야 한다.
① LINQ의 구현 원리를 이해 하려면
=> 확장 메소드(extension method) 문법
=> Delegate(Func)와 람다 표현식
=> Coroutine 개념
// 1번
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.MySelect(n => n * 10);
foreach(int n in e)
Console.WriteLine(n);
}
}
public static class MyLinq {
public static IEnumerable<int> MySelect(this Array arr) {
}
}
이제 MySelect를 만들어 볼 예정이다.
현재 배열(arr)클래스에는 MySelect란 메소드가 없으므로 에러가 난다.
그런데 C#에는 기존 클래스에 메소드를 추가할 수 있는 문법이 있다. (확장 메소드)
확장 메소드는 static 클래스에 static 멤버로 만들면 된다.
그리고 this 클래스 변수명 을 인자로 받으면 된다.
그리고 return 타입은 IEnumerable<int>으로 주면 될 것이다.
이렇게 되면 이전에 확장 메소드에서 다뤘듯이 arr.MySelect...로 메소드를 호출하면 사실
MySelect(arr, ...) 로 불러오는 것이라 인자로 arr이 잘 전달되었다고 할 수 있다.
그리고 9번 라인의 람다표현식이 두번째 인자라고 할 수 있다.
// 2번
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.MySelect(n => n * 10);
foreach(int n in e)
Console.WriteLine(n);
}
}
public static class MyLinq {
public static IEnumerable<int> MySelect(this Array arr, Func<int, int> predicate) {
}
}
자 이제 람다 표현식을 인자로 받아야 하는데 이건 이전에 다룬 delegate를 받으면 된다.
형태는 int타입 인자를 받고, int를 return 하니 Func를 쓰면 된다.
이렇게 모양은 완성이 되었다.
이제 구현을 하면 된다.
// 3번
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.MySelect(n => n * 10);
foreach(int n in e)
Console.WriteLine(n);
}
}
public static class MyLinq {
public static IEnumerable<int> MySelect(this Array arr, Func<int, int> predicate) {
foreach(int n in arr) {
return predicate(n);
}
}
}
foreach문을 통해 반복을 하며 인자를 하나씩 정해진 방식으로 돌리면 되니 predicate를 이용해 인자를 요소를 보내면 된다.
이제 완성이 되었는데 에러가 난다.
잘 보면 MySelect는 IEnumerable<int>인 컬렉션 모양을 return해야 한다.
하지만 지금은 return 타입이 int이다.
그래서 그냥 return 하는것이 아닌 이전에 다룬 코루틴에서 배운것으로 yield를 사용한다.
// 4번
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.MySelect(n => n * 10);
foreach(int n in e)
Console.WriteLine(n);
}
}
public static class MyLinq {
public static IEnumerable<int> MySelect(this Array arr, Func<int, int> predicate) {
foreach(int n in arr) {
yield return predicate(n);
}
}
}
이렇게 하고 실행해보면 아무 문제 없이 실행되는 것을 볼 수 있다.
위의 코드들에 맨 첫번째 줄에 번호를 적어놨는데
1번 : 확장 메소드
2번 : delegate
3~4번 : 코루틴
이다.
링크를 걸어놨으니 확인을 하며 보면 좋을 것 같다.
3. 세부 내용
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.MySelect(n => n * 10);
IEnumerator<int> p = e.GetEnumerator();
while(p.MoveNext()) {
Console.WriteLine(p.Current);
}
}
}
public static class MyLinq {
public static IEnumerable<int> MySelect(this Array arr, Func<int, int> predicate) {
foreach(int n in arr) {
yield return predicate(n);
}
}
}
동작 원리를 좀 더 자세히 알아보자.
foreach를 지우고 while문으로 대신 했다.
사실 foreach의 원리 자체가 위의 11번~15번 라인이기 때문에 풀어 썼다.
이전 포스팅에서 코루틴의 동작은 호출 시에 실행되는게 아닌
열거자 호출 후 MoveNext()를 호출 했을 때 동작하게 된다고 했다.
다시 살펴보자면 로깅을 해서 확인해볼 수 있다.
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.MySelect(n => n * 10);
IEnumerator<int> p = e.GetEnumerator();
Console.WriteLine("열거자 호출");
while(p.MoveNext()) {
Console.WriteLine("요소 꺼내오기");
Console.WriteLine(p.Current);
}
}
}
public static class MyLinq {
public static IEnumerable<int> MySelect(this Array arr, Func<int, int> predicate) {
foreach(int n in arr) {
Console.WriteLine("MySelect");
yield return predicate(n);
}
}
}
일반적인 메소드라면
MySelect -> 열거자 호출 -> 요소 꺼내오기 순이 되야 겠지만
위의 코드는
열거자 호출 -> MySelect -> 요소 꺼내오기 순이 된다.
MySelect를 호출 할때 지금 함수를 호출 안하고 열거자를 만들고 MoveNext()일때 동작할게
가 된다.
4. MySelect 개선
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.MySelect(n => n * 10);
IEnumerator<int> p = e.GetEnumerator();
while(p.MoveNext()) {
Console.WriteLine(p.Current);
}
}
}
public static class MyLinq {
public static IEnumerable<int> MySelect(this Array arr, Func<int, int> predicate) {
foreach(int n in arr) {
yield return predicate(n);
}
}
}
현재 위의 코드의 문제점은 Array클래스를 받을 때만 된다는 것이다.
하지만 Linq는 모든 컬렉션에 다 적용시킬 수 있다.
그럼 이것을 이제 Generic으로 바꿔보자.
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
IEnumerable<int> e = arr.MySelect(n => n * 10);
IEnumerator<int> p = e.GetEnumerator();
while(p.MoveNext()) {
Console.WriteLine(p.Current);
}
}
}
public static class MyLinq {
public static IEnumerable<T> MySelect<T>(this IEnumerable<T> arr, Func<T, T> predicate) {
foreach(T n in arr) {
yield return predicate(n);
}
}
}
MySelect를 Generic으로 바꾸고 int가 들어가는 자리를 전부 T로 대체했다.
또, Array클래스를 받는 대신 IEnumerable<T>을 받게 했다.
모든 컬렉션은 IEnumerable<T>를 구현해야 하기 때문에 받을 수 있기 때문이다.
728x90
반응형
'프로그래밍 > C#' 카테고리의 다른 글
[C#] 스레드 개념 (Thread) (0) | 2020.05.08 |
---|---|
[C#] Fluent vs Query (LINQ Fluent vs Query Syntax) (0) | 2020.05.07 |
[C#] LINQ 개념 (Language INtegrated Query) (0) | 2020.05.06 |
[C#] 코루틴 (COROUTINE) (3) (0) | 2020.05.05 |
[C#] 코루틴 (COROUTINE) (2) (0) | 2020.05.05 |