외로운 Nova의 작업실

c# 언어 공부 - 10(LINQ) 본문

Programming/C#

c# 언어 공부 - 10(LINQ)

Nova_ 2022. 5. 11. 03:32

안녕하세요. 오늘은 저번시간에 이어서 c#의 LINQ에대해서 배워보도록 하겠습니다.

LINQ는 Language INtegrated Query의 약자입니다.

한국어로 직역하면 언어 통합 질의입니다.

의역하면 데이터 질의 기능이라고 할수 있습니다.

데이터를 한곳에 모아놓고 원하는 데이터를 질의하듯이 골라 낼 수 있는 기능을 가집니다.

백문일 불여일견이라고 코드를 한번 봅시다.

using System;
using System.Linq;

namespace StudyCSharp
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Profile[] profile_list =
        {
            new Profile() { name = "정우성", age = 13 },
            new Profile() { name = "김태희", age = 23 },
            new Profile() { name = "창모", age = 26 },
            new Profile() { name = "김하온", age = 20},
            new Profile() { name = "이병재", age = 24},
            new Profile() { name = "이순재", age = 70}

        };

            var profiles = from profile in profile_list
                           where profile.age < 30
                           orderby profile.name
                           select profile;

            foreach(Profile profile in profiles)
            {
                Console.WriteLine(profile.name + profile.age.ToString());
            }
        }
    }

    class Profile //사람에대한클래스
    {
        public string name //사람의 이름
        {
            get; set;
        }

        public int age //사람의 나이
        {
            get; set;
        }
    }
}

위코드는 사람에대한 class profile을 만들고 그의 객체를 생성하여 profile_list 배열에 담았습니다.

이후 profile_list에서는 아래와 같은 코드로 몇개의 객체를 생성하고 있습니다.

var profiles = from profile in profile_list
				where profile.age < 30
				orderby profile.name
				select profile;

위 구문은 아래와 같이 해석합니다.

1. from profile in profile_list : profile_list 배열에서 원소들을 profile에 할당한다.

2. where profile.age < 30 : 그 원소들중에서 age속성이 30이하인 값만 선택한다.

3. orderby profile.name : 위에서 선택한 원소들을 profile.name 오름차순 ㄱ ->ㅎ 순으로 정렬한다.

4. select profile : 이후 profile객체를 선택하여 var profiles에 넣는다.

위와 같은 방법으로 손쉽게 데이터를 골라낼수 있습니다.

 

그렇다면 골라낸 var profiles의 형식은 무엇일까요? LINQ의 질의 결과는 IEnumerable<T>로 반환됩니다.

T는 select문에서 결정됩니다.

만약 profile이 select 되었다면, IEnumerable<profile>이 반환됩니다.

만약 profile.name이 select 되었다면, IEnumerable<string>이 반한될 것 입니다.

 

<여러개의 데이터원본에 질의하기>

만약, 데이터를 모아놓은 배열안에 또다른 배열이 있다면 어떻게 골라내는지 보겠습니다.

코드를 보시죠.

using System;
using System.Linq;

namespace StudyCSharp
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Student[] student_list =
        {
            new Student() { name = "정우성", score = new int[]{11, 23, 33, 12 } },
            new Student() { name = "김태희", score = new int[]{23, 25, 33, 12 } },
            new Student() { name = "창모", score = new int[]{22, 12, 42, 21 } },
            new Student() { name = "김하온", score = new int[]{24, 11, 24, 24 } },
            new Student() { name = "이병재", score = new int[]{53, 12, 22, 31 } },
            new Student() { name = "이순재", score = new int[]{44, 52, 12, 12 }}

        };

            var students = from student in student_list //student_list에서 객체를 student에 할당
                           from score in student.score //할당된 student객체의 score배열의 원소를 score에 할당
                           where score >30 //score이 30보다 큰 것만 걸러내고
                           select new {student.name , score}; //student.name이랑 score만 저장

            foreach(var student in students)
            {
                Console.WriteLine(student.name + student.score);
            }
        }
    }

    class Student //사람에대한클래스
    {
        public string name //사람의 이름
        {
            get; set;
        }

        public int[] score;//학생의 점수들
    }
}

student 객체안에는 score이란 점수 배열이 있습니다.

또한 student_list 배열안에는 student 객체들이 존재합니다.

이처럼 2개의 배열이 있을때 from in 구문을 2번 이용하여 객체들을 골라낼 수 있습니다.

위의 결과는 아래와 같습니다.

 

<group by로 데이터 분류하기>

where문으로 데이터를 분류할 수 도 있지만, group by로 데이터를 분류할 수 도 있습니다.

하지만 쓰임이 조금은 다른데요, 먼저 코드를 봅시다.

using System;
using System.Linq;

namespace StudyCSharp
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Student[] student_list =
        {
            new Student() { name = "정우성", score = 23 },
            new Student() { name = "김태희", score = 25, },
            new Student() { name = "창모", score =  42 },
            new Student() { name = "김하온", score = 11},
            new Student() { name = "이병재", score = 22},
            new Student() { name = "이순재", score = 22 }
        };

            var students = from student in student_list
                           group student by student.score >= 25 into g
                           select new { group_key = g.Key, student = g};

            foreach(var student_group in students) //Key값이 true인 배열과 false인 배열을 student_group에 담는다
            {
                Console.WriteLine($"점수가 25점 이상인가요?{student_group.group_key}");

                foreach(var student in student_group.student)
                {
                    Console.WriteLine($"{student.name} : {student.score}");
                }
            }
        }
    }

    class Student //사람에대한클래스
    {
        public string name //사람의 이름
        {
            get; set;
        }

        public int score;//학생의 점수들
    }
}

위코드는 학생에대한 클래스를 만들고 점수와 이름을 담는 속성을 만듭니다.

이후 학생들을 여러 객체를 만들어 student_list에 담아놉니다.

이후 student_list에서 데이터를 뽑으려고 합니다.

아래는 LINQ구문입니다.

            var students = from student in student_list
                           group student by student.score >= 25 into g
                           select new { group_key = g.Key, student = g};

1. from student in student_list : student_list에서 배열의 원소들 즉 학생 객체들을 student에 담습니다.

2. group student by student.score >= 25 into g : student객체를 student.score이 25보다 큰 학생이랑 아닌학생을 그룹지어서 g 그룹에 저장합니다. 이때, g에는 g.Key값이 존재합니다. score이 25보다 크면 true가 저장되고 작으면 false가 저장됩니다. 떠힌 그 Key값에 맞는 객체들을 배열안에 넣어서 그 배열을 g에 할당합니다.

특히 foreach문을 사용할때, 원래는 안의 원소들이 하나씩 할당되지만 group 객체일경우 Key값에 따라서 true값과 false 값이 하나씩 할당됩니다.

따라서 위의 코드에서 foreach문이 2개인걸 알 수 있습니다.

위 코드의 실행결과는 아래와 같습니다.

LINQ group문

 

<내부 join>

두 원본 데이터를 합칠때 join문을 사용하면 편리합니다.

예를 들어 아래와 같은 데이터들이 있을때,

{name = "", score = }, {name = "", height = }

name이라는 공통된 요소를 가지고 score과 height을 하나의 데이터로 만들 수 있다는 뜻입니다.

코드를 보시죠.

using System;
using System.Linq;

namespace StudyCSharp
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Student_Score[] student_scores =
            {
                new Student_Score(){name = "창모", score = 12},
                new Student_Score(){name = "제키와이", score = 21},
                new Student_Score(){name = "페이커", score = 55},
                new Student_Score(){name = "장기하", score = 42},
                new Student_Score(){name = "아이유", score = 33},
                new Student_Score(){name = "더콰이엇", score = 44}
            };

            Student_Height[] student_heights =
            {
                new Student_Height(){name = "창모", height = 188 },
                new Student_Height(){name = "뷔", height = 177 },
                new Student_Height(){name = "정우성", height = 189 },
                new Student_Height(){name = "현아", height = 163 },
                new Student_Height(){name = "페이커", height = 177 },
                new Student_Height(){name = "아이유", height = 162 },
            };

            var students = from student_score in student_scores
                           join student_height in student_heights on student_score.name equals student_height.name
                           select new
                           {
                               name = student_score.name,
                               score = student_score.score,
                               height = student_height.height
                           };

            foreach(var student in students)
            {
                Console.WriteLine($"이름 : {student.name}, 점수 : {student.score}, 키 : {student.height}");
            }
        }   
    }

    class Student_Score
    {
        public string name //학생의 이름
        {
            get; set;
        }

        public int score;//학생의 점수들
    }

    class Student_Height
    {
        public string name //학생의 이름
        {
            get; set;
        }
        public int height; //학생의 키
    }
}

이름과 점수가 있는 Student_Score 객체를 모아놓은 student_scores[] 배열과 이름과 키가 있는 Student_Height 객체를 모아놓은 student_height[] 배열이 있습니다.

이 두개의 데이터를 LINQ하는 구문을보면 아래와 같습니다.

            var students = from student_score in student_scores
                           join student_height in student_heights on student_score.name equals student_height.name
                           select new
                           {
                               name = student_score.name,
                               score = student_score.score,
                               height = student_height.height
                           };

join student_height in student_heights on student_score.name equals student_heught 구문은 아래와 같이 해석합니다.

student_heights 배열에서 객체들을 하나씩 student_height에 담고 student_score.name과 studnet_height.name을 비교해서, 이름이 같은 student_score객체만 남기고 버립니다.

주요한점은 처음 선택한 객체를 선택한다는 것입니다.

 

위코드는 아래의 결과를 갖습니다.

LINQ join 구문

 

<외부 join>

앞에서 배운 join은 내부 조인으로 equal값이 맞는 값만 select 됩니다.

이를 내부join이라고 합니다.

또한, equal값이 맞는 값과 맞지않는 값 둘다 selct 하는것을 외부 join이라고합니다.

외부 join은 group 구문에서 사용햇던 into 연산자를 사용해서 구현합니다.

또한 equal값이 아닌 속성을 디폴트 값으로 초기화하는 메소드는 DefultIfEmpty()입니다.

먼저 코드부터 보시죠.

using System;
using System.Linq;

namespace StudyCSharp
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Student_Score[] student_scores =
            {
                new Student_Score(){name = "창모", score = 12},
                new Student_Score(){name = "제키와이", score = 21},
                new Student_Score(){name = "페이커", score = 55},
                new Student_Score(){name = "장기하", score = 42},
                new Student_Score(){name = "아이유", score = 33},
                new Student_Score(){name = "더콰이엇", score = 44}
            };

            Student_Height[] student_heights =
            {
                new Student_Height(){name = "창모", height = 188 },
                new Student_Height(){name = "뷔", height = 177 },
                new Student_Height(){name = "정우성", height = 189 },
                new Student_Height(){name = "현아", height = 163 },
                new Student_Height(){name = "페이커", height = 177 },
                new Student_Height(){name = "아이유", height = 162 },
            };

            var students = from student_score in student_scores
                           join student_height in student_heights on student_score.name equals student_height.name into ps
                           from student_height in ps.DefaultIfEmpty(new Student_Height() { height = 0 })
                           select new
                           {
                               name = student_score.name,
                               score = student_score.score,
                               height = student_height.height
                           };

            foreach(var student in students)
            {
                Console.WriteLine($"이름 : {student.name}, 점수 : {student.score}, 키 : {student.height}");
            }
        }   
    }

    class Student_Score
    {
        public string name //학생의 이름
        {
            get; set;
        }

        public int score;//학생의 점수들
    }

    class Student_Height
    {
        public string name //학생의 이름
        {
            get; set;
        }
        public int height; //학생의 키
    }
}

위 코드의 실행결과는 아래와 같습니다.

 

LINQ 외부 join

 

여기서 사용되는 데이터의 키나 점수는 임의로 넣은것이니 오해없길 바랍니다.

 

Comments