코다람쥐 2022. 4. 22. 23:13

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}");
                }
            }

일반버전&nbsp; 모두 출력
LINQ버전 Hp만 출력

 

그리고 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);
            }

 

두 개의 구문은 결과가 완전히 같다. 문법에 대해서만 유심히 살펴보고 표준 연산자가 조금 더 많은 기능을 표현할 수 있다는것만 알아두자.