LINQ
1. LINQ의 필요성
Q) 플레이어의 레벨 기준으로 오름차순으로 정렬하는 코드.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
namespace CSharp
{
public enum ClassType
{
Knight,
Archer,
Mage
}
public class Player
{
public ClassType classType { get; set; }
public int Level { get; set; }
public int Hp { get; set; }
public int Attack { get; set; }
}
class Program
{
static List<Player> _players = new List<Player>();
static void Main(string[] args)
{
Random rand = new Random();
for(int i = 0; i < 100; i++)
{
ClassType type = ClassType.Knight;
switch(rand.Next(0, 3))
{
case 0:
type = ClassType.Knight;
break;
case 1:
type = ClassType.Archer;
break;
case 2:
type = ClassType.Mage;
break;
}
Player player = new Player()
{
classType = type,
Level = rand.Next(0, 100),
Hp = rand.Next(100, 1000),
Attack = rand.Next(5, 50)
};
_players.Add(player);
}
List<Player> players = GetHightLevelKnights();
foreach(Player player1 in players)
{
Console.WriteLine($"직업 : {player1.classType}, 레벨 : {player1.Level}, Hp : {player1.Hp}, 공격력 : {player1.Attack}");
}
}
// Q) 레벨이 50 이상인 Knight를 출력해서 레벨기준 오름차순으로 정렬
public static List<Player> GetHightLevelKnights()
{
List<Player> players = new List<Player>();
int i, j;
for(i = 0; i < _players.Count; i++)
{
if (_players[i].Level >= 50 && _players[i].classType == ClassType.Knight)
players.Add(_players[i]);
}
int idx;
Player temp;
for(i = 0; i < players.Count - 1; i++)
{
idx = i;
for(j = i + 1; j < players.Count; j++)
{
if (players[idx].Level > players[j].Level) idx = j;
}
temp = players[idx];
players[idx] = players[i];
players[i] = temp;
}
return players;
}
}
}
플레이어의 모든 정보를 랜덤함수를 통해 저장하였고
Knight타입의 레벨을 오름차순으로 정렬하여 출력하는 코드이다.
그리고 위의 코드를 LINQ를 이용하여 구현할 수 있다.
2. LINQ 사용
//일반 버전
{
List<Player> players = GetHightLevelKnights();
foreach (Player player1 in players)
{
Console.WriteLine($"직업 : {player1.classType}, 레벨 : {player1.Level}, Hp : {player1.Hp}, 공격력 : {player1.Attack}");
}
}
일반적으로 위의 코드로 출력을 표현했다면 LINQ버전은 다음 코드처럼 표현할 수 있다.
//LINQ 버전
{
var players =
from p in _players
where p.classType == ClassType.Knight && p.Level >= 50
orderby p.Level
select p;
Console.WriteLine();
foreach (Player player1 in players)
{
Console.WriteLine($"직업 : {player1.classType}, 레벨 : {player1.Level}, Hp : {player1.Hp}, 공격력 : {player1.Attack}");
}
}
마치 SQL문법과 거의 흡사하다. 그래도 조금은 다른면이 있으니 살펴보자.
우선 from의 의미로 기존의 SQL에서는 테이블을 나타냈지만 C#에서 from은 아래 코드의 의미와 비슷하다.
foreach (Player p in _players)
from p에서 p는 우리가 임의로 정해준 별칭이다.
where, orderby는 SQL과 비슷하다. 각각 조건설정과 정렬기능이다.
select는 가공해서 추출하는 부분이다. 만약에 p.Hp라고 하면 Hp의 정보만 추출할 수 있다. 아래의 코드는 select를 가공해서 추출하는 코드이다.
//일반 버전
{
List<Player> players = GetHightLevelKnights();
foreach (Player player1 in players)
{
Console.WriteLine($"직업 : {player1.classType}, 레벨 : {player1.Level}, Hp : {player1.Hp}, 공격력 : {player1.Attack}");
}
}
// LINQ버전 : Hp정보만 추출
{
var players =
from p in _players
where p.classType == ClassType.Knight && p.Level >= 50
orderby p.Level
select p.Hp;
Console.WriteLine();
foreach (int p in players)
{
Console.WriteLine(p);
//Console.WriteLine($"직업 : {p.classType}, 레벨 : {p.Level}, Hp : {p.Hp}, 공격력 : {p.Attack}");
}
}


그리고 new를 이용해서 새로운 인스턴스를 생성할 수도 있다.
var players =
from p in _players
where p.classType == ClassType.Knight && p.Level >= 50
orderby p.Level
select new { Hp = p.Hp, Level = p.Level * 2 };
// Hp는 그대로 만들고 Level은 추출한 레벨의 2배로 인스턴스 생성
다만 LINQ는 유니티에서 사용을 잘 안한다. 이유는 버그가 터진다고 한다..
그래서 웹 서버에서만 사용하는 것을 권유한다.
그래도 장점이라면 가독성이 좋고 MS제품군과 호환이 잘 된다는 점에서 무조건 알아야 하는 기능이다.
3. 중첩 from
중첩foreach문처럼 from을 중첩해서 쓸 수 있다.
public class Player
{
public ClassType classType { get; set; }
public int Level { get; set; }
public int Hp { get; set; }
public int Attack { get; set; }
public List<int> Items { get; set; } = new List<int>();
}
먼저 Player클래스에 List<int> Items를 추가한다.
그 다음 Main문안에서 리스트에 임의의 데이터 5개를 추가한다.
for( int j = 0; j < 5; j++)
{
player.Items.Add(rand.Next(1, 101));
}
마지막으로 아래와같이 from을 중첩해서 사용하면된다. 중첩foreach문처럼 생각해도 된다.
//중첩 from
// ex) 모든 아이템 목록을 추출 (아이템 id < 30를 추출)
{
var playerItems =
from p in _players
from i in p.Items
where i < 30
select new { p, i };
var li = playerItems.ToList();
}
4. Grouping
SQL의 GROUP BY와 HAVING의 비슷한 기능도 사용할 수 있다.
// Grouping
{
var playerBylevel =
from p in _players
group p by p.Level into g // g는 그룹의 이름
orderby g.Key
select g;
Console.WriteLine("\n------------Grouping 버전--------------");
foreach (var Players in playerBylevel)
{
int i = 1;
Console.WriteLine($"\n key : {Players.Key}레벨 그룹");
foreach (var groups in Players)
{
Console.WriteLine($"{i++} 번째 유저의 공격력 : {groups.Attack}, Hp : {groups.Hp}");
}
}
}
5. Join
SQL의 Join의 사용방법은 아래와 같다.
// Join(내부 조인)
// outer join(외부 조인)
{
// 레벨이 1, 5, 10인 데이터만 추출
List<int> levels = new List<int>() { 1, 5, 9 };
var playerLevels = from p in _players
join l in levels // p와 l을 비교
on p.Level equals l // 레벨이 같은지 비교
orderby p.Level // 정렬
select p; // Player형의 데이터로 추출
}
6. LINQ 표준 연산자
// LINQ 표준 연산자
{
var players =
from p in _players
where p.classType == ClassType.Knight && p.Level >= 50
orderby p.Level
select p;
var players2 = _players
.Where(p => p.classType == ClassType.Knight && p.Level >= 50)
.OrderBy(p => p.Level)
.Select(p => p);
}
두 개의 구문은 결과가 완전히 같다. 문법에 대해서만 유심히 살펴보고 표준 연산자가 조금 더 많은 기능을 표현할 수 있다는것만 알아두자.