블로그를 작성하며

해당 글은 같은 동기 분들의 이해를 돕기 위해 나름 공부하여 쉽게 풀어서
이해를 돕기위해 제작 되었습니다.
LINQ의 경우 기본적인 반복문을 꼭❗️❗️ 능숙하게 이해를 하시고 나서
사용하시는 것을 추천 드립니다.
부디 LINQ의 이해하시는데 도움이 되길 바랍니다.

LINQ

LINQ : 데이터 질의 기능

  • Language INtergrated Query
  • 기본적인 데이터 질의문
    • From : 어떤 데이터 집합에서 찾을 것인가요?
    • Where : 어떤 값의 데이터를 찾을 것인가요?
    • Select : 어떤 항목을 추출할 것인가요?

언제 LINQ를 사용하나요?

  • 만약 임의의 리스트 안에서 조건에 맞는 일부 요소만을 추출하고 싶다고 가정한다면 다음과 같이
    예시를 만들 수 있습니다.
  • 일반적으로 foreach를 사용하여 리스트를 순회하면서 해당 요소만을 추출하는 방법입니다.

foreach를 사용한 데이터 추출 및 정렬

private void LINQ()  
{  
	List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };  
	List<int> evens = new List<int>();  
	  
	// numbers 라는 List 데이터 중  
	// List 안에서 짝수만을 추출하가 위한 반복문  
	foreach (int number in numbers)  
	{  
		// 만약 numbers 리스트의 요소 중  
		// 2로 나누어 나머지가 0이면  
		if (number % 2 == 0)  
		{  
			// evens 라는 새로운 리스트에 할당하여  
			// 짝수 리스트를 만듭니다.  
			evens.Add(number);  
		}  
	}  

	// 짝수 리스트를 내림차순으로 정렬하기  
	evens.Sort((a,b) => b - a );  
	
	evens : {10, 8, 6, 4, 2}
}

쿼리를 사용한 데이터 추출 및 정렬

private void LINQ()  
{  
	List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };  
	  
	List<int> evens = (  
		// number라는 변수는 numbers 라는 컬렉션의 안에 있는 데이터 입니다.  
		// 여기서 number는 1 ~ 10 까지의 컬렉션 안의 데이터 입니다.  
		from number in numbers  
		// number 가 2로 나눌때 나머지가 0이면  
		where number % 2 == 0  
		// number를 내림차순으로 정렬하고  
		orderby number descending  
		// 내림차순으로 정렬된 number를 선택하여  
		select number)  
		// 리스트로 만들어 줍니다.  
		.ToList();  
	  
	evens : {10, 8, 6, 4, 2}  
}

LINQ로 변형된 데이터 추출 및 정렬

private void LINQ()  
{  
	List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };  
	  
	List<int> evens = numbers  
	// 범위변수 number 가 2로 나뉠때 나머지가 0인 것들을  
	.Where(number => number % 2 == 0)  
	// 내림차순으로 정렬하여  
	.OrderByDescending(number => number)  
	// 리스트로 만들어 줍니다.  
	.ToList();  
	  
	evens : {10, 8, 6, 4, 2}  
}

범위변수?

foreach 문의 반복 변수 number는 numbers 라는 원본으로부터 데이터를 담아내지만,
LINQ 안에 number범위변수 라고 해서 실제로 데이터를 담지는 않고,
오로지 LINQ 안에서만 사용되며, 어떤 일이 어떻게 일어나는지만 묘사하는데만 사용
foreach 또는 for 문에서 사용하는 i 또는 위 첫번째 예제처럼 실제 변수를 반복적으로 담아서 실제 변수로 사용이 가능 evens.Add(number)하지만

LINQ에서 사용하는 x,y 또는 위 예제에서 사용한 number는 실제 변수에 대입되지 않고
numbernumber처럼 실행된다 라는 표현입니다.

표준 LINQ 연산 메서드 정리

종 류 메 서 드 설 명 지연 실행
정렬



OderBy 오름차순으로 값을 정렬 🧨 지연 실행
OderByDescending 내림차순으로 값을 정렬 🧨 지연 실행
ThenBy 오름차순으로 2차 정렬 🧨 지연 실행
ThenByDescending 내림차순으로 2차 정렬 🧨 지연 실행
Reverse 컬렉션 요소의 순서를 거꾸로 뒤집기 🧨 지연 실행
집합


Distinct 중복값을 제거 🧨 지연 실행
Except 두 컬렉션 사이의 차집합을 반환
임의의 컬렉션(a,b,c,e)에는 존재하는데
다른 컬렉션(a,d,f)에는 존재하지 않는 요소
(b,e)를 반환
🧨 지연 실행
Intersect 두 컬렉션 사이의 교집합을 반환
두 컬렉션 양쪽 모두 존재하는 요소만 반환
🧨 지연 실행
Union 두 컬렉션 사이의 합집합을 반환
한쪽 컬렉션이 a,b,c,d 요소를 갖고 있고
다른 한쪽 컬렉션이 a,b,d,e 요소를 갖고 있다면
두 컬렉션 사이의 합집합은 a,b,c,d,e를 반환
🧨 지연 실행
필터링
OfType 메소드 형식 매개변수로 형식 변환이 가능한 값들만 추출 🧨 지연 실행
Where 필터링할 조건을 평가하는 함수를 통과하는 값들만 추출 🧨 지연 실행
수량
연산

All 모든 요소가 임의의 조건을 모두 만족시키는지 평가
결과는 true, false 둘중 하나를 반환
🧨 지연 실행
Any 모든 요소 중 단 하나의 요소라도 임의의 조건을 만족시키는지 평가
결과는 true, false 둘중 하나를 반환
🧨 지연 실행
Contains 명시한 요소가 포홤되어 있는지 평가
true, false를 반환
🔫 즉시 실행
데이터
추출
Select 값을 추출하여 시퀀스를 만듬 🧨 지연 실행
SelectMany 여러 개의 데이터 원본으로부터 값을 추출하여 시퀀스를 생성
여러개의 from절을 사용
🧨 지연 실행
데이터
분할


Skip 시퀀스에서 지정한 위치까지 요소들은 건너 뜀 🧨 지연 실행
SkipWhile 입력된 조건 함수를 만족시키는 요소는 건너뜀 🧨 지연 실행
Take 시퀀스에서 지정한 요소까지 요소들을 취합 🧨 지연 실행
TakeWhile 입력된 조건 함수를 만족시키는 요소들을 취합 🧨 지연 실행
데이터
결합
Join 공통 특성을 가진 서로 다른 두 개의 데이터 소스를 연결
공통 특성을 Key로 삼아서 Key가 일치하는 두 객체를 쌍으로 추출
🧨 지연 실행
GroupJoin 기본적으로 Join 연산자와 같은 일을 하되,
조인 결과를 그룹으로 만들어 넣음
🧨 지연 실행
데이터
그룹화
GroupBy 공통 특성을 공유하는 요소들을 각 그룹으로 묶음
각 그룹은 IGrouping<TKey, TElement>객체로 표현
🧨 지연 실행
ToLookUp Key 선택 함수를 이용하여 골라낸 요소들을
Lookup<Tkey, TElement> 형식의 객체에 삽입
하나의 Key에 여러 개의 객체를 대응시킬 때 사용하는 컬렉션
🧨 지연 실행
생성


DefaultEmpty 빈 컬레션을 기본값이 할당된 싱글턴 컬렉션으로 바꿈 🧨 지연 실행
Empty 비어 있는 컬렉션을 반환 🧨 지연 실행
Range 일정 범위의 숫자 시퀀스를 담고 있는 컬렉션을 생성 🧨 지연 실행
Repeat 같은 값이 반복되는 컬렉션을 생성 🧨 지연 실행
동등
평가
SequenceEqual 두 시퀀스가 서로 일치하는지를 평가 🔫 즉시 실행
요소
접근






ElementAt 컬렉션으로부터 임의의 인덱스존재 요소를 반환 🔫 즉시 실행
ElementOrDefault 컬렉션으로부터 임의의 인덱스에 존재하는 요소를 반환
인덱스가 컬레션의 범위를 벗어날 때 기본값을 반환
🔫 즉시 실행
First 컬렉션의 첫 번째 요소를 반환
조건식이 매개변수로 입력되는 경우 이 조건을 만족하는
첫 번째 요소를 반환
🔫 즉시 실행
FirstOrDefault First와 동일 하지만 반환값이 없을때 기본값을 반환 🔫 즉시 실행
Last 컬렉션의 마지막 요소를 반환 🔫 즉시 실행
LastOrDefault Last 연산자와 같은 기능을 하되
반환값이 없으면 기본값을 반환
🔫 즉시 실행
Single 컬렉션의 유일한 요소를 반환 🔫 즉시 실행
SingleOrDefault Single과 동일한 기능
유일값이 아니거나, 반환값이 없으면 기본값을 반환
🔫 즉시 실행
형식
변환






AnEnumerable 매개변수를 IEnumerable<T>로 형식 변환하여 반환 🧨 지연 실행
AsQueryable IEnumable 객체를
=> IQueryable 형식으로 변환
🧨 지연 실행
Cast 컬렉션의 요소들을 특정 형식으로 변환 🧨 지연 실행
OfType 특정 형식으로 형식 변환할 수 있는 값만 걸러냄 🧨 지연 실행
ToArray 컬렉션을 배열로 반환 => 강제로 쿼리를 실행시킴 🧨 지연 실행
ToDictionary Key 선택 함수에 근거하여
컬렉션 요소를 Dictonary<TKey, TValue>에 삽입
=> 강제로 쿼리를 실행시킴
🧨 지연 실행
ToList 컬렉션을 List«code class=”language-plaintext highlighter-rouge”>T</code>> 형식으로 변환 => 강제로 쿼리를 실행 🧨 지연 실행
ToLookup Key 선택 함수에 근거해서 컬렉션의 요소를
Lookup<TKey, TElement>에 삽입 => 강제로 쿼리를 실행
🧨 지연 실행
연결 Concat 두 시퀀스를 하나의 시퀀스로 연결 🧨 지연 실행
집계





Aggregate 컬렉션의 각 값에 대해 사용자가 정의한 집계 연산을 수행 🔫 즉시 실행
Average 컬렉션의 각 값에 대한 평균을 계산 🔫 즉시 실행
Count 컬렉션에 조건에 부합하는 요소의 개수를 카운팅 🔫 즉시 실행
LongCount 매우 큰 컬렉션을 카운팅 🔫 즉시 실행
Max 컬렉션의 가장 큰 값을 반환 🔫 즉시 실행
Min 컬렉션의 가장 작은 값을 반환 🔫 즉시 실행
Sum 컬렉션 안에서의 값의 합을 계산 🔫 즉시 실행

자주 사용 되는 LNIQ

해당 예제들은 이해하기 쉬우시도록 LINQ 버전일반적인 Loop 방식을 같이 기입하였고
LINQ 방식의 경우 해당 메소드들을 하나만 쓰는 것이 아닌 다양한 조합으로
더욱 복잡한 로직을 수행할 수 있습니다.

정렬

  • OderBy : 오름차순 정렬
List<int> numbers = new List<int> { 1, 3, 2, 4, 10, 6, 5, 8, 9, 7 };  
  
LINQ 방식
{
	List<int> evens = numbers.Where(n => n % 2 == 0).OrderBy(n => n).ToList();

	결괏  => {2, 4, 6, 8, 10};
}


일반적인 Loop 방식
{
	List<int> evens = new List<int>();
	
	foreach(int n in numbers)
	{
		if (n % 2 == 0)
		{
			evens.Add(n);
		}
	}
	
	evens.Sort();
	
	결괏  => {2, 4, 6, 8, 10};
}
  • OderByDescending : 내림차순 정렬
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };  

LINQ 방식
{
	List<int> evens = 
		numbers.Where(n => n % 2 == 0).OrderByDescending(n => n).ToList();
	
	결괏  => {10, 8, 6, 4, 2};
}

일반적인 Loop 방식
{
	List<int> evens = new List<int>();
	
	foreach(int n in numbers)
	{
		if (n % 2 == 0)
		{
			evens.Add(n);
		}
	}

	evens.Sort((a,b) => b - a);

	결괏  => {10, 8, 6, 4, 2};
}
  • Reverse : 컬렉션 거꾸로 뒤집기
List<int> numbers = new List<int> { 1, 3, 2, 4, 10, 6, 5, 8, 9, 7 };

LINQ 방식
{
	List<int> evens = numbers.Where(n => n % 2 == 0).Reverse().ToList();
	
	결괏  => {8, 6, 10, 4, 2};
}

일반적인 Loop 방식
{
	List<int> evens = new List<int>();

	foreach(int n in numbers)
	{
		if(n % 2 == 0)
		{
			evens.Add();
		}
	}

	evens.Reverse();

	결괏  => {8, 6, 10, 4, 2};
}

집합

  • Distinct : 컬렉션 내부의 중복값을 제거
List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 5, 5, 6 };  

LINQ 방식
{
	List<int> distinctValue = numbers.Distinct().ToList();
	
	결괏  => {1, 2, 3, 4, 5, 6};
}

일잔적인 Loop 방식
{
	List<int> distincValue = new List<int>();

	foreach(int n in numbers)
	{
		if (!distincValue.Contains(n))
		{
			distincValue.Add(n);
		}
	}
	
	결괏  => {1, 2, 3, 4, 5, 6};
}

필터링

  • Where : 필터링하여 조건에 맞는 값들을 반환
List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 5, 5, 6};  

LINQ 방식
{
	List<int> filterNumbers = numbers.Where(n => n > 3).ToList();
	
	결괏  => {4, 5, 5, 6};
}

일반적인 Loop 방식
{
	List<int> filterNumbers = new List<int>();

	foreach(int n in numbers)
	{
		if(n > 3)
		{
			filterNumbers.Add(n);
		}
	}

	결괏  => {4, 5, 5, 6};
}

수량연산

  • All : 모든 요소가 조건을 만족하는지 평가
List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 5, 5, 6};  

LINQ 방식
{
	bool number = numbers.All(n => n > 5);
	
	결괏  => false;
	
	bool number = number.All(n => 0 < n);
	
	결괏  => true;
}

일반적인 Loop 방식
{
	bool number = true;

	foreach (int n in numbers) 
	{
		if (!(n > 5))
		{ 
			number = false; 
			break; 
		} 
	}
	
	결괏  => false;


	foreach (int n in numbers) 
	{
		if (!(0 < n))
		{ 
			number = false; 
			break; 
		} 
	}

	결괏  => true;
}
  • Any : 모든 요소 중 단 하나의 요소라도 임의의 조건을 만족하는지 평가
List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 5, 5, 6};  

LINQ 방식
{
	bool number = numbers.Any(n => n > 5);
	
	결괏  => true;
}

일반적인 Loop 방식
{
	bool number = false;
	
	foreach(int n in numbers)
	{
		if(n > 5)
		{
			number = true;
			break;
		} 
	}
}
  • Contains : 명시된 요소가 포함 되어있는지 평가
List<string> fruits = new List<string> { "사과", "배", "귤", "바나나"};

LINQ 방식
{
	bool containBucket = fruits.Contains("포도");
	
	결괏  => false;
}

일반적인 Loop 방식
{
	bool containBucket = false;
	
	foreach(string f in fruits)
	{
		if (f == "포도")
		{
			containBucket = true;
			break;
		}
	}
}

데이터 추출

  • Select : 값을 추출하여 시퀀스를 만듬

🌱 시퀀스 ? 시퀀스 (Sequence) 는 기존 컬랙션의 요소를 반환하는 것이 아닌
해당 요소를 바탕으로 새로운 컬랙션 요소를 만드는것이라고 이해하시는게 쉽습니다.

예를 들어 { 사과, 바나나, 딸기 } 의 새로운 시퀀스는
{ 애플파이, 바나나 와플, 딸기 페스츄리 } 처럼 새로운 요소를 생성하는 것 입니다.

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

LINQ 방식
{
	List<int> squares = numbers.Select(n => n * n).ToList();

	결괏  => {1, 4, 9, 16, 25}
}

일반적인 Loop 방식
{
	List<int> squares = new List<int>(); 
	
	foreach (int n in numbers)
	{ 
		squares.Add(n * n); 
	}
	결괏  => {1, 4, 9, 16, 25}
}
  • SelectMany : 여러개의 데이터 원본의 값을 추출하여 시퀀스를 생성
List<List<int>> listOfLists = new List<List<int>> 
{
    new List<int> { 1, 2, 3 },
    new List<int> { 4, 5, 6 },
    new List<int> { 7, 8, 9 }
};

LINQ 방식
{
	List<int> flattenedList = listOfLists.SelectMany(subList => subList)
								.ToList();

	결괏  => { 1, 2, 3, 4, 5, 6, 7, 8 ,9 }
}

일반적인 Loop 방식
{
	List<int> flattenedList = new List<int>(); 
	
	foreach (List<int> subList in listOfLists) 
	{ 
		foreach (int item in subList)
		{ 
			flattenedList.Add(item); 
		} 
	}

	결괏  => { 1, 2, 3, 4, 5, 6, 7, 8 ,9 }
}

데이터 분할

  • Skip : 시퀀스에서 지정한 위치까지 요소들을 건너 뜀
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 

LINQ 방식
{
	List<int> skippedNumbers = numbers.Skip(3).ToList();

	결괏  => { 4, 5, 6, 7, 8, 9 }
}

일반적이 Loop 방식
{
	List<int> skippedNumbers = new List<int>(); 
	
	int skipCount = 3; 
	
	foreach (int n in numbers) 
	{ 
		if (skipCount > 0) 
		{ 
			skipCount--; 
			continue;
		} 
		skippedNumbers.Add(n); 
	}
	
	결괏  => { 4, 5, 6, 7, 8, 9 }
}
  • Take : 시퀀스에서 지정한 요소까지 요소들을 취합
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

LINQ 방식
{
	List<int> firstThreeNumbers = numbers.Take(3).ToList();

	결괏  => { 1, 2, 3 }
}

일반적인 Loop 방식
{
	List<int> firstThreeNumbers = new List<int>();

	int takeCount = 3; 
	foreach (int n in numbers) 
	{     
		if (takeCount == 0)  
	    {         
		    break;  
		}     
		firstThreeNumbers.Add(n);     
		takeCount--; 
	}
	
	결괏  => { 1, 2, 3 }
}

데이터 그룹화

  • GroupBy : 공통 특성을 공유하는 요소를 각 그룹으로 묶음 ( IGrouping<TKey, TElement> 객체 )
public class NBCampManager
{ 
	public string Name { get; set; } 
	public int Age { get; set; } 
}

List<NBCampManager> manager = new List<NBCampManager> 
{ 
	/ Age 임의로 넣은  입니다.
	/ 오해하지 말아주세요.
	
	new NBCampManager { Name = "한효승", Age = 30 }, 
	new NBCampManager { Name = "장윤서", Age = 25 }, 
	new NBCampManager { Name = "박준영", Age = 26 }, 
	new NBCampManager { Name = "이서연", Age = 25 }, 
	new NBCampManager { Name = "정승호", Age = 26 } 
};

LINQ 방식
{
	나이를 기준으로 그룹을 생성
	var groupedByAge = manager.GroupBy(p => p.Age).ToList();

	 3가지의 그룹이 생성  {25 그룹}, {26 그룹}, {30 그룹}
	결괏  => {"장윤서", "이서연"}, {"박준영", "정승호"}, {"한효승"}
}

일반적인 Loop 방식
{
	foreach (var m in manager) 
	{ 
		if (!manualGroups.ContainsKey(m.Age)) 
		{ 
			manualGroups[m.Age] = new List<NBCampManager>(); 
		} manualGroups[m.Age].Add(m); 
	} // Now, iterating over the manual groups 
	
	foreach (var group in manualGroups) 
	{ 
		Console.WriteLine($"Age Group: {group.Key}"); 
		foreach (var person in group.Value) 
		{ 
			Console.WriteLine($" - {person.Name}"); 
		} 
	}
}

🫠 그룹핑의 경우 이해가 쉽도록 반환값을 바로 볼 수 있도록 표시해 두었지만,
해당 값들은 IGrouping<TKey, TElement> 타입으로 Key 값Value 값을 이용하셔야
정상적으로 사용이 가능합니다.
사용법 자체는 Dictionary 데이터 타입의 사용방법과 거의 동일 합니다.

요소접근

  • FirstOrDefault : 컬렉션의 첫 번째 요소를 반환하고 값이 없으면 기본값 을 반환
List<int> numbers = new List<int> { 7, 8, 9, 10, 11, 12, 13 };

LINQ 방식
{
	int firstNumber = numbers.FirstOrDefault(n => n > 10);

	결괏  => 11; 

	int defualtNumber = number.FirstOrDefault(n => n < 7);

	결괏  => 0;
}

일반적인 Loop 방식
{
	int firstNumberGreaterThanTen = 0;  
	
	foreach (int n in numbers) 
	{     
		if (n > 10)     
		{         
			firstNumberGreaterThanTen = n;         
			break;     
		} 
	}
	결괏  => 11;

	foreach (int n in numbers) 
	{     
		if (n < 7)     
		{         
			firstNumberGreaterThanTen = n;         
			break;     
		} 
	}
	결괏  => 0;
}

형식변환 (즉시 실행)

  • ToArray : 컬렉션을 배열로 변환
  • ToList : 컬렉션을 리스트로 변환
  • ToDictionary : 컬렉션을 딕셔너리로 변환
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 

LINQ 방식
{
	int[] numberArray = numbers.Where(n => n > 3).ToArray();

	numberArray = { 4, 5, 6, 7, 8, 9 };
}

연결

  • Concat : 두 시퀀스를 하나의 시퀀스로 연결
List<int> firstList = new List<int> { 1, 2, 3 };
List<int> secondList = new List<int> { 4, 5, 6 };

LINQ 방식
{
	IEnumerable<int> concatenatedList = firstList.Concat(secondList);

	concatenatedList : {1,2,3}
}

집계

  • Aggregate : 컬렉션의 각 값에 대해 사용자가 정의한 집계 연산을 수행
List<int> numbers = new List<int> { 1, 2, 3, 4 };

LINQ 방식
{
	int product = numbers.Aggregate((acc, n) => acc * n);

	product : 24;
}

일반적인 Loop 방식
{
	int product = 1; 
	
	foreach (int n in numbers) 
	{ 
		product *= n; 
	}

	product : 24;
}
  • Average : 컬렉션의 각 값에 대한 평균을 계산
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; 

LINQ 방식
{
	double average = numbers.Average();

	average = 3.0;
}

일반적인 Loop 방식
{
	double sum = 0; 
	foreach (int n in numbers) 
	{ 
		sum += n; 
	} 
	
	double average = sum / numbers.Count;
}
  • Count : 컬렉션에 조건에 부합하는 요수의 개수를 카운팅
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

LINQ 방식
{
	int totalCount = numbers.Count();

	totalCount : 5;

	int countGreaterThanThree = numbers.Count(n => n > 3);

	countGreaterThanThree : 3;
}

일반적인 Loop 방식
{
	int totalCount = 0; 
	foreach (int n in numbers) 
	{ 
		totalCount++; 
	}

	totalCount : 5;

	int countGreaterThanThree = 0; 
	foreach (int n in numbers) 
	{ 
		if (n > 3) 
		{ 
			countGreaterThanThree++; 
		} 
	}

	countGreaterThanThree : 3;
}
  • Max : 컬렉션의 최대값을 반환
  • Min : 컬렉션의 최소값을 반환
  • Sum : 컬렉션의 합을 반환
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

LINQ 방식
{
	int maxNumber = numbers.Max();

	maxNumber : 5;
	
	int minNumber = numbers.Min();

	minNumber : 1;
	
	int sumOfNumbers = numbers.Sum();

	sumOfNumbers : 15;
}

일반적인 Loop 방식
{
	int maxNumber = int.MinValue; 
	foreach (int n in numbers) 
	{ 
		if (n > maxNumber) 
		{ 
			maxNumber = n; 
		} 
	}

	maxNumber : 5;


	int minNumber = int.MaxValue;

	foreach (int n in numbers)
	{
	    if (n < minNumber)
	    {
	        minNumber = n;
	    }
	}

	minNumber : 1;

	
	int sumOfNumbers = 0;

	foreach (int n in numbers)
	{
	    sumOfNumbers += n;
	}

	sumOfNumbers : 15;
}

⏰ 지연실행 (Deferred Excution) ⏰

  • LINQ가 데이터에 대한 쿼리를 정의하고, 실제로 그 결과가 필요할 때 까지 실행을 지연시키는 중요한 특징

지연 실행의 원리

쿼리 정의

  • LINQ는 실행될 때 결과를 즉시 생성하지 않음
  • 쿼리 자체의 실행 계획을 정의

실행 시점

  • 실제 데이터에 대한 연산은 쿼리 결과가 반복되거나 (foreach, ToList, ToArray 등을 사용시)
  • 쿼리에 기반한 어떤 연산 (Count, Max, First 등)이 호출될 때 수행

반복 가능성

  • 쿼리는 여러 번 반복될 수 있으며, 각 반복마다 쿼리는 다시 평가되고
  • 데이터 소스에 대한 새로운 요청이 이루어짐

지연 실행의 장점

효율성

  • 쿼리 결과가 필요하지 않으면, 데이터 소스에 대한 연산이 수행되지 않아 리소스를 절약

유연성

  • 쿼리 실행 전에 쿼리를 수정하거나 추가할 수 있어, 동적 쿼리 생성이 용이

성능 최적화

  • 전체 데이터 컬렉션을 메모리에 로드할 필요 없이 필요한 부분만 처리할 수 있어 성능이 향상

지연 실행의 예시

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

int query = numbers.Where(n => n > 3); //query를 정의하는 시점 (아직 실행되지 않습니다.)

numbers.Add(6); // query 정의 후 데이터를 추가하여 변경

foreach(int number in query) // 이곳에서 이제야 query를 실행
{
	Console.WriteLine(number);  
	
	결괏  => 4, 5, 6 출력
}
  • Where 쿼리는 foreach루프를 만나기 전까지는 실행되지 않음
  • numbers 리스트에 6이 추가된 후 쿼리가 실행되어 최신 데이터가 반영됨

주의사항

데이터 소스 변경

  • 지연 실행은 데이터 소스가 쿼리 정의 후 변경될 경우, 변경 사항을 반영
  • 얘기치 않은 결과가 나올 수 있어 실행 순서를 꼭 유념해야합니다.

부작용

  • 쿼리의 지연 실행으로 인해, 예상보다 늦게 실행되어 부작용이 발생할 수 있음
  • 특히 외부 데이터 소스(Database)를 사용할 때 주의 해야합니다.

성능 문제

  • 지연 실행은 매번 쿼리를 실행할 때마다 데이터 소스를 평가합니다.
  • 따라서, 같은 쿼리를 반복적으로 사용할 경우 예상치 못한 성능 저하가 발생할 수 있습니다.
  • 특히 데이터 소스가 크거나, 외부 데이터베이스나 파일 시스템과 같이 느린 입출력을 사용할 때 문제가 될 수 있습니다.

쿼리 실행 타이밍의 오해

  • 지연 실행으로 인해 쿼리가 언제 실제로 실행되는지 혼란스러워할 수 있습니다.
  • 예를 들어, foreach문이나 ToList(), ToArray()와 같은 쿼리를 실제로 평가하는 메서드 호출 전까지는 데이터가 처리되지 않습니다.

리소스 관리 문제

  • 지연 실행을 사용할 때, 리소스(예: 데이터베이스 연결)가 언제 해제될지 예측하기 어려울 수 있습니다.
  • 쿼리가 실행되는 동안 리소스가 계속 열려있어야 하며, 적절한 시점에 리소스를 해제하는 것이 중요합니다.

마치며

LINQ 자체는 잘 사용하면 코드의 가독성을 높히고, 복잡한 연산을 쉽게 처리할 수 있지만
분명히 기본적인 반복문에 대해서 명확히 숙지를 하시고 나서 사용하시기를 바랍니다.

지금은 이해가 안가시는게 당연한 일이니 시간을 두고 공부하시기를 바랍니다.

이번 블로그를 정리하면서 저도 많은 공부가 되었습니다.

감사합니다.