일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 플러터
- Unity
- docker
- Python
- c# winform
- 깃
- Flutter
- Data Structure
- gitlab
- vim
- 다트 언어
- HTML
- C++
- Algorithm
- c언어
- c# 추상 클래스
- git
- 유니티
- c# 윈폼
- C# delegate
- github
- dart 언어
- 도커
- jupyter
- jupyter lab
- C언어 포인터
- 구조체
- c#
- Houdini
- 포인터
- Today
- Total
nomad-programmer
[Programming/C#] LINQ (링크) 본문
LINQ는 매우 깔끔하고 쉽게 읽고 쓸 수 있지만, 일반적으로 알고리즘을 수동으로 작성하는 것보다 훨씬 더 많은 계산, 특히 더 많은 메모리 할당이 필요하다. 이 점을 반드시 알고 있어야 한다.
using System.Linq;
List<int> data = new List<int>();
data.Any(x => x > 10);
var result = from x in data
where x > 10
select x;
그렇기때문에 Unity에서는 LINQ를 사용하지 않도록해야한다.
https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/let-clause
LINQ는 데이터베이스의 SQL과 똑같다고 생각하면 된다.
LINQ는 원래 DBMS에서 사용하던 SQL(Structured Query Language)을 본떠 프로그래밍 언어 안에 통합한 것이다. LINQ가 SQL과 상당히 닮아 있긴 하지만, SQL은 그 자체로도 커다란 프로그래밍 언어이다. 지원하는 데이터 형식도 프로그래밍 언어보다 훨씬 다양하고, 데이터를 다루는 데 필요한 함수, 연산자 등이 다양하면서 사용하기 쉽게 설계되어 있다.
데이터를 미디어에서 읽고, 거르고, 정렬하는 작업은 프로그램에서 부지기수로 일어난다. 이러한 데이터 작업은 당연히 프로그래머가 프로그래밍해 넣어야 한다. 이런 지루한 작업은 양도 적지 않다.
LINQ는 이 지루한 데이터 작업에서부터 해당시켜준다. LINQ는 "Language INtegrated Query" 의 약어이며 C# 언어에 통합되 데이터 질의 기능을 말한다.
- From : 어떤 데이터 집합에서 찾을 것인가?
- Where : 어떤 값의 데이터를 찾을 것인가?
- Select : 어떤 항목을 추출할 것인가?
var profiles = from profile in arrProfile // arrProfile 안에 있는 각 데이터로부터
where profile.Height < 175 // Height가 175 미만인 객체만 골라
orderby profile.Height // 정렬하여
select profile; // profile 객체를 추출한다.
foreach(var profile in profiles)
Console.WriteLine("{0}, {1}", profile.Name, profile.Height);
from
모든 LINQ 쿼리식(Query Expression)은 반드시 from 절로 시작한다. 쿼리식의 대상이 될 데이터 원본과 데이터 원본 안에 들어 있는 각 요소 데이터를 나타내는 범위 변수를 from 절에서 지정해줘야 한다.
이 때 from의 데이터 원본은 아무 형식이나 사용할 수 없고, IEnumerable<T> 인터페이스를 상속하는 형식이어야만 한다.
LINQ의 범위 변수와 foreach 문의 반복 변수의 차이점
foreach 문의 반복 변수는 데이터 원본으로부터 데이터를 담아낸다. 하지만 범위 변수는 실제로 데이터를 담지는 않는다. 그래서 퀀리식 외부에서 선언된 변수에 범위 변수의 데이터를 복사해 넣는다던가 하는 일은 할 수 없다.
범위 변수는 오로지 LINQ 질의 안에서만 통용되며, 질의가 실행될 때 어떤 일이 일어날지를 묘사하기 위해 도입되었다.
LINQ 질의어를 이용한 예제
using System;
using System.Linq;
namespace test
{
internal class Program
{
public static void Main(string[] args)
{
int[] numbers = {7, 5, 9, 2, 1, 3, 8, 10, 4, 6};
var result = from n in numbers
where n % 2 == 0
orderby n ascending
select n;
foreach (int n in result)
Console.WriteLine(n);
}
}
}
/* 결과
2
4
6
8
10
*/
from 절을 이용해서 데이터 원본으로부터 범위 변수를 뽑아낸 후, LINQ가 제공하는 수십 가지 연산자를 이용해 데이터를 가공 및 추출할 수 있다.
where
where는 한마디로 필터(Filter) 역할을 하는 연산자다. from 절이 데이터 원본으로부터 뽑아낸 범위 변수가 가져야 하는 조건을 where 연산자에 매개 변수로 입력하면 LINQ는 해당 조건에 부합하는 데이터만을 걸러낸다.
orderby
orderby는 데이터의 정렬을 수행하는 연산자이다. orderby 연산자는 기본적으로 오름차순으로 데이터를 정렬하지만 더 명확하게 오름차순으로 정렬한다는 것을 명시해주는 것이 가독성에 좋다.
asceding 은 오름차순, descending 은 내림차순이다.
select
select 절은 최종 결과를 추출하는 쿼리식의 마침표 같은 존재이다. from 절에서 데이터 원본으로부터 범위 변수를 뽑아내고 where 절에서 이 범위 변수의 조건을 검사한 후, 그 결과를 orderby절에서 정렬하고 select 문을 이용해서 최종 결과를 추출해낸다.
var형식으로 선언된 profiles의 실제 형식은?
C# 컴파일러가 var형식을 LINQ 쿼리식이 반환할 결과 형식에 맞춰 알아서 컴파일해주기는 하지만, 실제로 var가 어떤 형식으로 치환되는지를 당연히 알아야 한다.
LINQ 질의 결과는 IEnumerable<T>로 반환된다. 이 때 형식 매개 변수 T는 select문에 의해 결정된다.
var profiles = from profile in arrProfile
where profile.Height < 175
orderby profile.Height
// select profile;
select profile.Name;
예를 들어 위 LINQ 쿼리식은 배열로부터 Height가 175미만인 profile 객체를 골라내는데, 그 결과는 IEnumerable<Profile> 형식이 된다. 만약 select문에서 Profile 객체 전체가 아닌 Name 프로퍼티만 추출하면 profiles는 IEnumerable<string> 형식으로 컴파일된다.
select문은 무명 형식을 이용해서 다음과 같이 새로운 형식을 즉석에서 만들어 낼 수도 있다.
var profiles = from profile in arrProfile
where profile.Height < 175
orderby profile.Height
select new {Name = profile.Name, InchHeight = profile.Height * 0.393};
쿼리식을 이용한 데이터 조회 및 가공 예제
using System;
using System.Collections.Generic;
using System.Linq;
namespace test
{
class Profile
{
public string Name { get; set; }
public int Height { get; set; }
}
internal class Program
{
public static void Main(string[] args)
{
Profile[] arrProfile =
{
new Profile() {Name = "C#", Height = 182},
new Profile() {Name = "C++", Height = 185},
new Profile() {Name = "Java", Height = 172},
new Profile() {Name = "Python", Height = 179},
new Profile() {Name = "Swift", Height = 171}
};
var profiles = from profile in arrProfile
where profile.Height < 175
orderby profile.Height ascending
select new
{
Name = profile.Name,
InchHeight = profile.Height * 0.393
};
foreach (var profile in profiles)
{
Console.WriteLine($"{profile.Name}, {profile.InchHeight}");
}
}
}
}
/* 결과
Swift, 67.203
Java, 67.596
*/
중첩 from
LINQ 쿼리식은 데이터 원본에 접근하기 위해 from 절을 사용한다. 여러 개의 데이터 원본에 접근하려면 이 from 문을 중첩해서 사용하면 된다.
중첩 from 예제
using System;
using System.Collections.Generic;
using System.Linq;
namespace test
{
class Class
{
public string Name { get; set; }
public int[] Score { get; set; }
}
internal class Program
{
public static void Main(string[] args)
{
Class[] arrClass = new Class[]
{
new Class() {Name = "A반", Score = new int[] {99, 88, 77, 25}},
new Class() {Name = "B반", Score = new int[] {60, 38, 82, 70}},
new Class() {Name = "C반", Score = new int[] {95, 33, 85, 91}},
new Class() {Name = "D반", Score = new int[] {99, 96, 2, 18}}
};
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.WriteLine($"{c.Name}, {c.Lowest}");
}
}
}
}
/* 결과
D반, 2
D반, 18
A반, 25
C반, 33
B반, 38
*/
group by (데이터 분류)
group by는 기준에 따라 데이터를 그룹화해준다. group by절은 다음의 형식으로 사용한다.
group A by B into C
- A : from 절에서 뽑아낸 범위 변수
- B : 분류 기준
- C : 그룹 변수
group by 예제
using System;
using System.Linq;
namespace test
{
class Profile
{
public string Name { get; set; }
public int Height { get; set; }
}
internal class Program
{
public static void Main(string[] args)
{
Profile[] arrProfile = new Profile[]
{
new Profile() {Name = "C#", Height = 182},
new Profile() {Name = "C++", Height = 159},
new Profile() {Name = "Java", Height = 172},
new Profile() {Name = "Python", Height = 179},
new Profile() {Name = "TypeScript", Height = 171}
};
var listProfile = from profile in arrProfile
orderby profile.Height
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
C++, 159
TypeScript, 171
Java, 172
- 175cm 미만? : False
Python, 179
C#, 182
*/
join
join은 두 데이터 원본을 연결하는 연산이다. 각 데이터 원본에서 특정 필드의 값을 비교하여 일치하는 데이터끼리 연결을 수행한다.
내부 조인 (inner join)
내부 조인은 교집합과 비슷하다. 첫 번째 데이터 원본의 데이터를 기준으로해서 이 데이터의 특정 필드와 두 번째 데이터 원본이 갖고 있는 각 데이터의 특징 필드를 비교해서 일치하는 데이터들만 모아 반환한다.
join 절의 on 키워드는 조인 조건을 수반한다. 이 때 on 절의 조인 조건은 "동등(Equality)"만 허용한다. 기본 연산자 중 하나인 "==" 연산자가 아닌, "equals" 라는 키워드가 조인을 위해 사용된다는 것을 주의해야 한다.
from a in A
join b in B on a.XX equals b.YY
외부 조인 (outer join)
외부 조인은 기본적으로 내부 조인과 비슷하지만, 기준이 되는 데이터 원본이 모두 포함된다는 점이 다르다. 연결할 데이터 원본에 기준 데이터 원본의 데이터와 일치하는 데이터가 없다면 그 부분은 빈 값으로 결과를 채우게 된다.
내부 조인, 외부 조인 등 SQL에서 본뜬 것이다. 원래 SQL에서 지원하는 외부 조인에는 왼쪽 조인(Left Join), 오른쪽 조인(Right Join), 완전 외부 조인(Full Outer Join) 이렇게 세 가지가 있다. 왼쪽 조인은 왼쪽 데이터 원본을 기준으로 삼아 조인을 수행하고, 오른쪽 조인은 오른쪽 데이터 원본을 기준으로 삼는다. 완전 외부 조인은 왼쪽과 오른쪽 데이터 원본 모두를 기준으로 삼는다.
LINQ는 이 세 가지 조인 방식 중에서 왼쪽 조인만을 지원한다. 허나 LINQ를 사용하다 보면 왼쪽 조인만으로도 부족함 없이 데이터를 다룰 수 있다는 것을 알게 될 것이다.
외부 조인을 사용하는 방법은 내부 조인과 크게 다르지 않다. 먼저 join 절을 이용해서 조인을 수행한 후 그 결과를 임시 컬렉션에 저장하고, 이 임시 컬렉션에 대해 DefaultIfEmpty 연산을 수행해서 비어 있는 조인 결과에 빈 값을 채워 넣는다.
DefaultIfEmpty 연산을 거친 임시 컬렉션에서 from 절을 통해 범위 변수를 뽑아내고, 이 범위 변수와 기준 데이터 원본에서 뽑아낸 범위 변수를 이용해서 결과를 추출해낸다.
내부 조인 및 외부 조인 예제
using System;
using System.Linq;
namespace test
{
class Profile
{
public string Name { get; set; }
public int Height { get; set; }
}
class Product
{
public string Title { get; set; }
public string Language { get; set; }
}
internal class Program
{
public static void Main(string[] args)
{
Profile[] arrProfile = new Profile[]
{
new Profile() {Name = "C#", Height = 182},
new Profile() {Name = "C++", Height = 159},
new Profile() {Name = "Java", Height = 172},
new Profile() {Name = "Python", Height = 179},
new Profile() {Name = "TypeScript", Height = 171}
};
Product[] arrProduct = new Product[]
{
new Product() {Title = "WebDevel", Language = "TypeScript"},
new Product() {Title = "Unity", Language = "C#"},
new Product() {Title = "Unreal", Language = "C++"},
new Product() {Title = "Server", Language = "C++"},
new Product() {Title = "Deep Learning", Language = "Python"}
};
var listProfile =
from profile in arrProfile
join product in arrProduct on profile.Name equals product.Language
select new
{
Name = profile.Name, Work = product.Title, Height = profile.Height
};
Console.WriteLine("-- 내부 조인 결과 --");
foreach (var profile in listProfile)
{
Console.WriteLine("이름: {0}, 작업: {1}, 크기: {2}",
profile.Name, profile.Work, profile.Height);
}
listProfile =
from profile in arrProfile
join product in arrProduct on profile.Name equals product.Language into ps
from product in ps.DefaultIfEmpty(new Product() {Title = "아직 없음"})
select new
{
Name = profile.Name, Work = product.Title, Height = profile.Height
};
Console.WriteLine();
Console.WriteLine("-- 외부 조인 결과 --");
foreach (var profile in listProfile)
{
Console.WriteLine("이름: {0}, 작업: {1}, 크기: {2}",
profile.Name, profile.Work, profile.Height);
}
}
}
}
/* 결과
-- 내부 조인 결과 --
이름: C#, 작업: Unity, 크기: 182
이름: C++, 작업: Unreal, 크기: 159
이름: C++, 작업: Server, 크기: 159
이름: Python, 작업: Deep Learning, 크기: 179
이름: TypeScript, 작업: WebDevel, 크기: 171
-- 외부 조인 결과 --
이름: C#, 작업: Unity, 크기: 182
이름: C++, 작업: Unreal, 크기: 159
이름: C++, 작업: Server, 크기: 159
이름: Java, 작업: 아직 없음, 크기: 172
이름: Python, 작업: Deep Learning, 크기: 179
이름: TypeScript, 작업: WebDevel, 크기: 171
*/
컴파일러가 바라보는 LINQ
C# 컴파일러는 어떻게 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
});
where는 Where() 메소드로, orderby는 OrderBy()로, select는 Select()로 바뀌었고 from절의 범위 변수 profile은 각 메소드에 입력되는 람다식의 매개 변수로 바뀌었다.
arrProfile 객체는 배열이다. 배열은 IEnumerable<T>의 파생 형식이며 System.Collections.Generic 네임스페이스 소속이다. 허나 여기서는 System.Linq 네임스페이스의 확장 메소드다. 이것으로 불러와야 에러가 나지 않는다. 따라서 이것들을 사용하려면 System.Linq 네임스페이스를 사용하도록 선언을 해둬야 한다.
LINQ 쿼리식을 메소드 호출 코드로 바꾼 예제
using System;
using System.Linq;
namespace test
{
class Profile
{
public string Name { get; set; }
public int Height { get; set; }
}
internal class Program
{
public static void Main(string[] args)
{
Profile[] arrProfile = new Profile[]
{
new Profile() {Name = "C#", Height = 182},
new Profile() {Name = "C++", Height = 159},
new Profile() {Name = "Python", Height = 172},
new Profile() {Name = "Java", Height = 178},
new Profile() {Name = "TypeScript", Height = 173}
};
var profiles = arrProfile
.Where(profile => profile.Height < 175)
.OrderBy(profile => profile.Height)
.Select(profile =>
new {Name = profile.Name, InchHeight = profile.Height * 0.393});
foreach (var profile in profiles)
{
Console.WriteLine($"{profile.Name}, {profile.InchHeight}");
}
}
}
}
/* 결과
C++, 62.487
Python, 67.596
TypeScript, 67.989
*/
'Programming > C#' 카테고리의 다른 글
[Programming/C#] 리플렉션 (Reflection) (0) | 2020.09.25 |
---|---|
[Programming/C#] LINQ의 표준 연산자와 쿼리식 문법 (0) | 2020.09.25 |
[Programming/C#] 식으로 이루어지는 멤버 (Member => Expression) (0) | 2020.09.24 |
[Programming/C#] Expression Tree (0) | 2020.09.24 |
[Programming/C#] Func 대리자, Action 대리자 (0) | 2020.09.23 |