갓똥
나는야 프로그래머
갓똥
전체 방문자
오늘
어제
  • 분류 전체보기 (186)
    • 프로그래밍 (146)
      • 자바 (9)
      • 안드로이드 (2)
      • 유니티 (20)
      • C++ (38)
      • C# (56)
      • HTML (2)
      • 파이썬 (3)
      • 자료구조 (2)
      • 알고리즘 (0)
      • 문제풀이 (4)
      • 디자인 패턴 (7)
      • 카카오톡 봇 (1)
      • 엑셀 (1)
      • 기타 (1)
    • 게임 (21)
      • 테일즈위버 (0)
      • 카이로소프트 (1)
      • 순위 (19)
      • 기타 (1)
    • 일상 (13)
      • 카페 (1)
      • 방탈출 (12)
    • 기타 (6)
      • 웃긴자료 (5)

블로그 메뉴

  • 홈
  • 방명록

공지사항

인기 글

태그

  • C++ 소멸자
  • c# Thread
  • 전세계게임매출순위
  • C++ virtual
  • C# 예외 처리
  • c# delegate
  • 알고리즘
  • 게임 디자인 패턴
  • Unity Graph
  • 게임 매출 순위
  • 글로벌게임매출
  • 유니티 그래프 그리기
  • 유니티 그래프
  • 자바
  • 롤 골드그래프
  • 강남 방탈출
  • c# unboxing
  • c# coroutine
  • pc게임 순위
  • c# collection
  • 게임매출순위
  • c# 코루틴
  • 모바일 게임 순위
  • 전세계 게임 매출
  • C# boxing
  • pc 게임 순위
  • C++ 상속
  • 유니티 골드그래프
  • 2020년 게임 매출
  • C++

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
갓똥

나는야 프로그래머

[C#] LINQ 원리
프로그래밍/C#

[C#] LINQ 원리

2020. 5. 7. 17:34
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
    '프로그래밍/C#' 카테고리의 다른 글
    • [C#] 스레드 개념 (Thread)
    • [C#] Fluent vs Query (LINQ Fluent vs Query Syntax)
    • [C#] LINQ 개념 (Language INtegrated Query)
    • [C#] 코루틴 (COROUTINE) (3)
    갓똥
    갓똥
    공부하며 알아가는 내용을 정리해 봅니다.

    티스토리툴바