C#

[C#] LINQ

Prooni 2024. 10. 13. 18:20

안녕하세요~

오늘은 C# Linq에 대해 공부해요!

 

LINQ는 Language INtegreted Query의 약어로

C#에 통합된 데이터 질의 기능이에요.

 

기본적으로 데이터에 대한 기본 질의는 다음과 같아요.

 

From : 어떤 데이터 집합에서 찾을 것인가?

Where : 어떤 값의 데이터를 찾을 것인가?

Select : 어떤 항목을 추출할 것인가?

 

From

쿼리식의 대상이 될 데이터 원본(Data Source)과 데이터 원본 안에 들어 있는

각 요소 데이터를 나타내는 범위 변수(Range Variable)를 From절에서 지정해줘요.

 

from의 데이터 원본은 아무 형식이나 사용할 수는 없고

IEnumerator<T> 인터페이스를 상속하는 형식이어야 해요.

 

Where

필터 역할을 하는 연산자에요.

범위 변수가 가져야 하는 조건을 where 연산자에 인수로 입력하면

LINQ는 해당 조건에 부합하는 데이터만 걸러내요.

 

var result = from n in numbers
    		 where n % 2 == 0
    		 orderby n
    		 select n;

 

Orderby

데이터의 정렬을 수행하는 연산자에요.

ascending 또는 descending 키워드를 통해

오름차순 또는 내림차순 정렬이 가능해요.

var result = from n in numbers
    		 where n % 2 == 0
    		 orderby n ascending // 또는 descending
    		 select n;

 

Select

최종 결과를 추출하는 쿼리식의 마침표 같은 존재에요.

LINQ 질의 결과 IEnumerable<T>로 반환되는데

이때 형식 매개변수 T는

select 문에 의해 결정되어요

 

아래처럼 Select문에 n의 이름만 추출하면

질의 결과는

IEnumerable<string> 형태로 컴파일 된답니다.

 

var result = from n in numbers
    		 where n % 2 == 0
    		 orderby n ascending // 또는 descending
    		 select n.Name;

 

 

select문은 무명 형식을 이용해서

새로운 형식을 즉석에서 만들어낼 수도 있어요.

var profiles = from profile in arrProfile
               where profile.Height < 175
               orderby profile.Height
               select new 
               {
                    Name = profile.Name,
                    InchHeight = profile.Height * 0.393
               };

 

 

예제코드 첨부할게요.

class Profile
{
    public string Name { get; set; }
    public int Height { get; set; }
}

class MainApp
{
    static void Main(string[] args)
    {
        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},
        };

        var profiles = from profile in arrProfile
                       where profile.Height < 175
                       orderby profile.Height
                       select new 
                       {
                            Name = profile.Name,
                            InchHeight = profile.Height * 0.393
                       };

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

    }
}

 

 

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

여러개의 데이터 원본에 접근하기 위해서는

from문을 중첩해서 사용해요.

 

아래 예제 코드는

arrClass 객체에 접근해서 변수 c를 뽑고

다시 변수 c의 객체의 score 필드에

또 다른 From 문으로 접근해요.

 

class Class
{
    public string Name { get; set; }
    public int[] Score { get; set; }
}

class MainApp
{
    static void Main(string[] args)
    {
        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 } },
        };

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

 

 

Group by

어떤 것을 특정 기준에 따라 나누어 정리하는 것을

group by 절을 통해 할 수 있어요.

 

 

group by 절의 형태는 아래와 같아요.

group A by B into C

 

A : from절에서 뽑아낸 범위변수

B:분류기준

C:그룹변수

 

예제코드 첨부할게요.

class Profile
{
    public string Name { get; set; }
    public int Height { get; set; }
}

class MainApp
{
    static void Main(string[] args)
    {
        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 },
        };
      
        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}");
            }
        }
    }
}

 

 

Join

join은 두 데이터 원본을 연결하는 연산이에요.

각 데이터의 원본에서 특정 필드값을 비교하여 일치하는 데이터끼리 연결해요.

내부조인과 외부조인이 있어요

 

 

Join - 내부 조인

내부조인은 교집합과 비슷해요.

두 데이터 원본 사이에서 일치하는 데이터들만 연결 후 반환해요

from a in A
join b in B on a.xxxx equals b.xxxx

데이터 A, B를 조인할때

on 절에 동등조건을 사용해요

on절에는 비교 연산은 허락되지 않는답니다!

 

 

Join - 외부 조인

외부 조인은 기본적으로 내부 조인과 비슷하지만

조인 결과에 기준이 되는 데이터 원본이 모두 포함된다는 점이 달라요!

 

외부 조인은 기준이 되는

데이터 원본의 모든 데이터를

조인 결과에 반드시 포함시켜요!

 

사용법

1. join 절을 이용해서 조인 수행 후 임시 컬렉션에 결과 저장

2. 임시 컬렉션에 대해 DefaultIfEmpty연산을 수행하여 비어 있는 조인 결과에 빈값을 채워넣기

3. DefaultIfEmpty 연산을 거친 임시 컬렉션에서 from 절을 통해 범위 변수를 뽑아내기

5. 4.에서 뽑아낸 범위 변수와 기준 데이터 원본에서 뽑아낸 범위 변수를 통해 결과 추출하기

 

내부조인과 외부조인에 대한 예제코드 첨부할게요.

class Profile
{
    public string Name { get; set; }
    public int Height { get; set; }
}

class Product
{
    public string Title { get; set; }
    public string Star { get; set; }
}

class MainApp
{
    static void Main(string[] args)
    {
        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 },
        };

        Product[] arrProduct =
        {
            new Product() { Title = "비트", Star = "정우성" },
            new Product() { Title = "CF 다수", Star = "김태희" },
            new Product() { Title = "아이리스", Star = "김태희"},
            new Product() { Title = "모래시계", Star = "고현정" },
            new Product() { Title = "Solo 예찬", Star = "이문세" },
        };

        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,
        };

        Console.WriteLine("--- 내부 조인 결과 ---");
        foreach(var profile in listProfile)
        {
            Console.WriteLine("이름:{0}, 작품:{1}, 키:{2}cm", profile.Name, profile.Work, profile.Height);
        }

        listProfile =
            from profile in arrProfile
            join product in arrProduct on profile.Name equals product.Star 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}cm", profile.Name, profile.Work, profile.Height);
        }
    }
}

 

 

LINQ 표준 연산자

Linq에는 53개의 다양한 표준 연산자가 존재해요

반면에 C# 쿼리식에서 지원하는 것은 11개 뿐이에요

 

따라서 LINQ 쿼리식과 메소드를 함께 사용하면 좋아요.

 

예제코드 첨부할게요.

class Profile
{
    public string Name { get; set; }
    public int Height { get; set; }
}

class MainApp
{
    static void Main(string[] args)
    {
        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 },
        };

        var heightStat = from profile in arrProfile
                         group profile by profile.Height < 175 into g
                         select new
                         {
                             Group = g.Key == true ? "175미만" : "175이상",
                             Count = g.Count(),
                             Max = g.Max(profile => profile.Height),
                             Min = g.Min(profile => profile.Height),
                             Average = g.Average(profile => profile.Height),
                         };

        foreach(var stat in heightStat)
        {
            Console.Write("{0} - Count:{1}, Max:{2}, ",
                stat.Group, stat.Count, stat.Max);
            Console.WriteLine("Min:{0}, Average:{1}",
                stat.Min, stat.Average);
        }
    }
}

'C#' 카테고리의 다른 글

[C#] Dynamic 형식  (2) 2024.10.15
[C#] 리플렉션과 애트리뷰트  (1) 2024.10.14
[C#] 식 트리 (Expression Tree)  (0) 2024.10.11
[C#] Func, Action  (0) 2024.10.11
[C#] 대리자와 이벤트 (delegate 와 event)  (5) 2024.10.10