2019
06.06

 

유니티 C# 프로그래밍에서 List를 다룰 때
List의 요소들을 다루는 일은 꽤나 코딩이 길어 질 수 있다.
하지만 LINQ를 사용한다면 꽤나 코드를 간결화 가능하다.

#. 첫번째 : List<Vector3> 에서 현재 위치와 2f 보다 멀리 떨어져 있는 요소 필터링하기 

    public List<Vector3> positionList; //원본 Vector 리스트
    public List<Vector3> filter_positionList; // 필터 후 담을 Vector 리스트
    
    // for문을 사용한 경우
    public void NormalFuntion()
    {
        for (int i = 0; i < positionList.Count; i++)
        {
            if (Vector3.Distance(transform.position, positionList[i]) > 2f)
                filter_positionList.Add(positionList[i]);
        }
    }
    
    // LINQ를 사용한 경우
    public void LINQFuntion()
    {
        filter_positionList = positionList
            .Where(n => Vector3.Distance(transform.position, n) > 2f)
            .ToList();
    }

이 필터링에서는 LINQ와 for문 구문의 차이가 거의 없게 비슷하다. 

하지만 규칙이 더 추가된다면 어떻게 될까?



#. 두번째 : List<Vector3> 에서 현재 위치와 2f 보다 멀리 떨어져 있는 요소를 오름차순으로 필터링하기 

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class MyTest : MonoBehaviour
{
    public List<Vector3> positionList; //원본 Vector 리스트
    public List<Vector3> filter_positionList; // 필터 후 담을 Vector 리스트

    // for문을 사용한 경우
    public void NormalFuntion()
    {
        filter_positionList = new List<Vector3>();
        List<Vector3> pre_filter_positionList = new List<Vector3>();

        for (int i = 0; i < positionList.Count; i++)
        {
            if (Vector3.Distance(transform.position, positionList[i]) > 2f)
                pre_filter_positionList.Add(positionList[i]);
        }
        
        for (int i = 0; i < pre_filter_positionList.Count; i++)
        {
            int l_higherNum = 0;

            for (int j = 0; j < filter_positionList.Count; j++)
            {
                if (Vector3.Distance(transform.position, pre_filter_positionList[i]) 
                    > Vector3.Distance(transform.position, filter_positionList[j]))
                    l_higherNum++;
            }

            filter_positionList.Insert(l_higherNum, pre_filter_positionList[i]);
        }

    }

    // LINQ를 사용한 경우
    public void LINQFuntion()
    {
        filter_positionList = new List<Vector3>();

        filter_positionList = positionList
            .Where(n => Vector3.Distance(transform.position, n) > 2f)
            .OrderBy(n => Vector3.Distance(transform.position, n))
            .ToList();
    }
}

 이제 상당한 차이를 보이기 시작한다. 명시성에도 차이가 나서
 for문은 주석이 없다면 어떤 필터링을 거쳤는지 알기도 힘들어진다.
 이렇듯 LINQ를 복잡한 필터링을 거칠 때 사용하기에 적합하다.


# LINQ 사용하기
LINQ는 데이터 소스를 가져오고, 쿼리 규칙을 만들고, 실행하는 3단계로 이루어져 있다.
앞서 설명했던 LINQ 구문에서는 간소화되어있었지만, 기본형식은 다음과 같이 IEnumerable을 사용한다.

IEnumerable<Vector3> l_filter_positionList = positionList // 데이터 소스 가져오기
    .Where(n => Vector3.Distance(transform.position, n) > 2f); // 쿼리 규칙 만들기

foreach (Vector3 position in l_filter_positionList) // 실행하기
{
    Debug.Log(position);
}

foreach등으로 열거자를 사용할 일이 없이 바로 리스트로 출력하고 싶은경우 아래와 같이 .ToList(); 를 사용해 고정시킨다.

var l_filter_positionList = positionList // 데이터 소스 가져오기
            .Where(n => Vector3.Distance(transform.position, n) > 2f) // 쿼리 규칙 만들기
            .ToList(); // 실행하기

 

#  Method syntax / Query syntax

LINQ는 Method syntax 형식과 Query syntax 두가지 표현방법이 있는데 다음과 같다.

public Vector3 add_positionList;
public void LINQFuntion1() // method syntax
{
    var l_filteredList = positionList
        .Where(n => Vector3.Distance(transform.position, n) > 2f)
        .ToList();
    }

public void LINQFuntion2() // query syntax
{
    var l_filteredList2 =
        (from n in positionList
        where Vector3.Distance(transform.position, n) > 2f
        select n).ToList();
}

유창한 구문의 경우 한줄로 풀어서 써보면 체인 형식으로 이루어 진 것을 확인 할 수 있다.

positionList.Where(n => Vector3.Distance(transform.position, n) > 2f).ToList();

 

# LINQ 사용하기
#. Where()
조건식이 true인 요소만 필터링한다.

// 예시 1
.Where(n => Vector3.Distance(transform.position, n) > 2f)
// 예시 2
.Where(n => n == new Vector3(-2f,0f,0f));

위와 같이 bool 로 표현되는 식으로 필터링합니다.


#. First()
첫 번째 요소를 반환

public void LINQFuntion()
{
    Vector3 firstPosition = positionList
        .Where(n => Vector3.Distance(transform.position, n) > 2f)
        .First(); // 첫 번쨰 요소를 반환한다.
}

 

#. OrderBy() / OrderByDescending() / Reverse()
오름차순 정렬 / 내림차순 정렬 / 요소의 순서를 반대로 정렬

public void LINQFuntion()
    {
        Vector3 firstPosition = positionList
            .Where(n => Vector3.Distance(transform.position, n) > 2f)
            .OrderBy(n => Vector3.Distance(transform.position, n))
            .First(); // 첫 번쨰 요소를 반환한다.
    }

 

# 지연된 처리 (Lazy)LINQ는 '지연된 처리'라는 중요한 특성을 가지고 있다.
데이터 소스를 가져오고 - 쿼리 식을 만들고 - 실행 하는 3단계로 진행되는데,

쿼리 식은 만들기만하고 실제 데이터의 처리는 실행 단계에서 모두 이루어진다.

즉 쿼리 식을 만들고 - 원본 데이터를 수정하고 - 실행 하는 식으로 중간 처리도 가능하다는 이야기.

public void LINQFuntion2()
    {
        var l_filteredList2 =
            from n in positionList
            where Vector3.Distance(transform.position, n) > 2f
            select n;

        positionList.Add(add_positionList); // 데이터 소스에 요소를 더한다.

        filter_positionList = l_filteredList2.ToList(); // 더해진 요소가 포함되어 연산된다.
    }

이 때문에 생길 수 있는 오류가 제법 예상되니, 아예 식을 만들면서 형변환을 통해 고정시켜버리는 방법도있다.

public void LINQFuntion1()
    {
        var l_filteredList = positionList
            .Where(n => Vector3.Distance(transform.position, n) > 2f)
            .ToList(); // 리스트로 고정시킨다.

        positionList.Add(add_positionList); // 원본데이터를 더한다.

        filter_positionList = l_filteredList; // 더해진 데이터가 반영되지않는다.
    }

 

 

 

 

 

COMMENT