본문 바로가기

C#/이것이 C#이다

15장. LINQ

반응형

1. 데이터! 데이터! 데이터!

LINQ(Language INtegrated Query): C# 언어에 통합된 데이터 질의 기능

질의(Query)란

- From: 어떤 데이터 집합에서 찾을 것인가
- Where: 어떤 값의 데이터를 찾을 것인가
- Select: 어떤 항목을 추출할 것인가

class Profile
{
    public string Name   { get; set; }
    public int    Height { get; set; }
}
Profile[] arrProfile = {
                           new Profile() { Name="정우성", Height=186 },
                           new Profile() { Name="김태희", Height=158 },
                           new Profile() { Name="고현정", Height=172 },
                           new Profile() { Name="이문세", Height=178 },
                           new Profile() { Name="하동훈", Height=171 }
                       };

LINQ가 아닌 코드

List<Profile> profiles = new List<Profile>();
foreach (Profile profile in arrProfile) // arrProfile 안의 각 데이터
{
    if (profile.Height < 175) // Height가 175 미만인 객체만
        profiles.Add(profile)
}

profiles.Sort((profile1, profile2) => {
    return profile1.Height - profile2.Height;
});

foreach (var profile in profiles)
    Console.WriteLine($"{profile.Name} {profile.Height}");

LINQ를 이용한 코드

var profiles = from    profile in arrProfile
               where   profile.Height < 175
               orderby profile.Heigh
               select  profile;

foreach (var profile in profiles)
    Console.WriteLine($"{profile.Name} {profile.Height}");

2. LINQ의 기본: FROM, WHERE, ORDERBY, SELECT

1. FROM

FROM의 데이터 원본은 아무 형식이나 사용할 수는 없고, IEnumerable<T> 인터페이스를 상속하는 형식이어야 함
foreach문의 반복 변수는 데이터 원본으로부터 데이터를 담아내지만, 범위 변수는 실제로 데이터를 담지는 않습니다.
따라서 쿼리식 외부에서 선언된 변수에 범위 변수의 데이터를 복사해 넣는다던가 하는 일은 할 수 없습니다.
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var result = from   n in numbers // n: 범위 변수, numbers: 데이터 원본
             where  n % 2 == 0
             order  by n
             select n;

2. WHERE

WHERE은 한 마디로 필터 역할을 하는 연산자입니다.
Profile[] arrProfile = {
                           new Profile() { Name="정우성", Height=186 },
                           new Profile() { Name="김태희", Height=158 },
                           new Profile() { Name="고현정", Height=172 },
                           new Profile() { Name="이문세", Height=178 },
                           new Profile() { Name="하동훈", Height=171 }
                       };
// 다음과 같은 WHERE 연산자를 이용해서 Height가 175 미만인 객체들을 걸러낼 수 있습니다.
var profiles = from   profile in arrProfile
               where  profile.Height < 175
               select profile;

3. ORDER BY

ORDERBY는 데이터의 정렬을 수행하는 연산자입니다.
// 다음과 같은 ORDERBY 연산자에 정렬의 기준이 되는 항목을 매개 변수로 입력해주면 됩니다.
var profiles = from    profile in arrProfile
               where   profile.Height < 175
               orderby profile.Height
               select  profile;
               orderby profile.Height ascending // 오름차순
               orderby profile.Height descending // 내림차순

4. SELECT

SELECT 절은 최종 결과를 추출하는 쿼리식의 마침표 같은 존재입니다.
// 다음과 같이 사용해주면 됩니다.
var profiles = from    profile in arrProfile
               where   profile.Height < 175
               orderby profile.Height
               select  profile.Name;
이렇게 사용하면 profiles는 IEnumerable<string> 형식으로 컴파일됩니다.
// 다음과 같이 무명 형식을 사용할 수도 있습니다.
var profiles = from    profile in arrProfile
               where   profile.Height < 175
               orderby profile.Height
               select  new{ Name = profile.Name, InchHeight = profile.Height * 0.393 };
무명 형식을 사용해서 새로운 형식을 즉석에서 만들어낼 수도 있습니다.

3. 여러 개의 데이터 원본에 질의하기

학급의 성적을 나타내는 클래스가 있고, 여러 학생의 점수를 담는 Score 필드가 배열로 있다고 가정해봅시다.
class Class
{
    public string Name { get; set; } 
    public int[]  Score { get; set; } // 배열
}
Class[] arrClass = 
{
    new Class() { Name="연두반", Score=new int[] { 99, 80, 70, 24 } },
    new Class() { Name="분홍반", Score=new int[] { 60, 45, 87, 72 } },
    new Class() { Name="파랑반", Score=new int[] { 92, 30, 85, 94 } },
    new Class() { Name="노랑반", Score=new int[] { 90, 88, 0, 17 } }
};
위 배열에서 점수가 60점 미만인 학생이 소속되어 있는 학급과 그 학생의 점수를 중첩한 from 절을 이용해서 뽑아보겠습니다.
var classes = from c in arrClass     // 첫 번째 데이터 원본
                  from s in c.Score  // 두 번째 데이터 원본
                  where s < 60
                  orderby s
              select new { c.Name, Lowest = s };

foreach (var c in classes)
	Console.WirteLine($"낙제 : {c.Name} ({c.Lowest})");
(결과)
낙제 : 노랑반 (0)
낙제 : 노랑반 (17)
낙제 : 연두반 (24)
낙제 : 파랑반 (30)
낙제 : 분홍반 (45)

4. GROUP BY로 데이터 분류하기

group A by B into C
A 절에는 from에서 뽑아낸 범위 변수를, B에는 분류 기준을, C는 그룹 변수를 위치시키면 됩니다.
Profile[] arrProfile = {
                           new Profile() { Name="정우성", Height=186 },
                           new Profile() { Name="김태희", Height=158 },
                           new Profile() { Name="고현정", Height=172 },
                           new Profile() { Name="이문세", Height=178 },
                           new Profile() { Name="하동훈", Height=171 }
                       };
이 데이터를 group by를 이용해서 분류해봅시다.
var listProfile = from profile in arrProfile
                      group profile by profile.Height < 175 into g
                      select new { GroupKey = g.Key, Profiles = g };
                      
foreach (var Group in listProfile)
{
    Console.WriteLine($"- 175cm 미만? : {Group.GroupKey}");
    foreach (var profile in Group.Profiles) {
    	Console.WriteLine($"    {profile.Name}, {profile.Height}");
    }
}
(결과)
- 175cm 미만? : True
    김태희, 158
    하하, 171
    고현정, 172
- 175cm 미만? : False
    이문세, 178
    정우성, 186

5. 두 데이터 원본을 연결하는 JOIN

1. 내부 조인

내부 조인: 교집합과 비슷, 두 데이터 원본 사이에서 일치하는 데이터들만 연결한 후 반환

from a in A
join b in B on a.XXXX equals b.YYYY
이때 on 절의 조인 조건은 "동등"만 허용하고, 기본 연산자인 "==" 연산자가 아닌, equals라는 키워드를 사용합니다.
var listProfile = 
    from profile in arrProfile
    join product in arrProduct on profile.Name equals product.Star
    select new
    {
        Name = profile.Name,
        Work = product.Title,
        Height = profile.Height
    };
(결과)
이름: 정우성, 작품: 비트, 키: 186
이름: 김태희, 작품: CF 다수, 키: 158
이름: 김태희, 작품: 아이리스, 키: 158
이름: 고현정, 작품: 모래시계, 키: 172
이름: 이문세, 작품: Solo 예찬, 키: 178

2. 외부 조인

외부 조인: 기본적으로 내부 조인과 비슷하지만, 조인 결과에 기준이 되는 데이터 원본이 모두 포함됩니다.

참고) LINQ가 지원하는 외부 조인은 왼쪽 조인만을 지원합니다.
var listProfile = 
    from profile in arrProfile
    join product in arrProduct on profile.Name equals product.Star into ps
    from product in ps.DefaultEmpty(new Product() { Title="그런거 없음" })
    select new
    {
        Name = profile.Name,
        Work = product.Title,
        Height = profile.Height
    };
(결과)
이름: 정우성, 작품: 비트, 키: 186
이름: 김태희, 작품: CF 다수, 키: 158
이름: 김태희, 작품: 아이리스, 키: 158
이름: 고현정, 작품: 모래시계, 키: 172
이름: 이문세, 작품: Solo 예찬, 키: 178
이름: 하하, 작품: 그런거 없음, 키: 171

6. LINQ의 비밀과 LINQ 표준 연산자

컴파일러는 어떻게 LINQ를 CLR이 이해하는 코드로 만들어내는 걸까요?

var profiles = from    profile in arrProfile
               where   profile.Height < 175
               orderby profile.Height
               select  new { Name = profile.Name, InchHeight = profile.Height * 0.393 };
C# 컴파일러는 다음과 같은 코드로 번역합니다.
var profiles = arrProfile
               .Where   ( profile => profile.Height < 175 )
               .OrderBy ( profile => profile.Height )
               .Select  ( profile => new {
                                             Name = profile.Name,
                                             InchHeight = profile.Height * 0.393
                                         });
LINQ 예제에서 늘 사용해왔던 arrProfile 객체는 배열입니다. 배열은 IEnumerable<T> 의 파생 형식이며, IEnumerable<T>는 System.Collections.Generic 네임스페이스 소속입니다. Where(), OrderBy(), Select() 등의 메소드는 System.Linq 네임스페이스에 정의되어있는 IEnumerable<T>의 확장 메소드입니다. 따라서 이들을 사용하려면 System.Linq 네임스페이스를 사용하도록 선언해야합니다.
다음은 LINQ 표준 연산자입니다. 53개의 표준 LINQ 연산 메소드 중에 C#의 쿼리식에서 지원하는 것은 달랑 11개 뿐입니다. 11가지만으로도 대부분의 데이터 처리가 가능하지만, 나머지 42개를 모두 활용할 수 있다면 편하겠죠?
 

[C#] LINQ 표준 연산자

LINQ 표준 연산자 종류  메소드 이름  설명  C# 쿼리식 문법 정렬  Order...

blog.naver.com

LINQ 쿼리식과 위 메소드를 함께 사용해봅시다. 키가 180cm 미만인 연예인들의 키 평균을 구해볼까요.
Profile[] arrProfile = {
                           new Profile() { Name="정우성", Height=186 },
                           new Profile() { Name="김태희", Height=158 },
                           new Profile() { Name="고현정", Height=172 },
                           new Profile() { Name="이문세", Height=178 },
                           new Profile() { Name="하동훈", Height=171 }
                       };
double Average = (from profile in arrProfile
                  where profile.Height < 180
                  select profile).Average(profile => profile.Height);
Console.WriteLine(Average); // 169.75 출력

 

반응형

'C# > 이것이 C#이다' 카테고리의 다른 글

14장. 람다식  (0) 2023.05.18
13장. 대리자와 이벤트  (0) 2023.05.18
11장. 일반화 프로그래밍  (0) 2023.05.18
10장. foreach가 가능한 객체 만들기  (0) 2023.05.18
9장. 프로퍼티  (0) 2023.05.16