private List<int> m_Temp = new List<int>();
private IEnumerable<int> m_TempEumerable => m_Temp;


private void TempMethod()
{
foreach (var t in m_Temp)
{
//ignore
}
foreach (var t in m_TempEumerable)
{
//ignore
}
}


여기서 TempMethod에서 m_Temp와 m_TempEnumerable의 차이가 뭘까?


메모장이 아니라 IDE를 쓰고있다면 이미 m_TempEnumerable을 foreach로 돌리려고 할때 할당이 일어난다고 알림을 띄울 것이다.


이건 컴파일러가 foreach를 처리할 때, 어떤 메서드를 우선적으로 호출하게 하는지에 대한 차이다.


일반적으로 생각했을때 foreach를 돌릴때 당연히 IEnumerable 인터페이스를 구현한 GetEnumerator를 사용한다고 여길것이다.


하지만 정확히 말하자면 foreach를 돌릴때 필요한 것은 적절한 Enumerator를 반환하는 GetEnumerator 메소드지, IEnumerable의 구현이 아니다.


이게 무슨 개소리지 싶겠지만, 실제로 적절한 Enumerator 반환하는 GetEnumerator 메소드를 구현한 타입은 IEnumerable을 상속하지 않고도


해당 타입을 foreach로 돌릴 수 있는걸 확인할 수 있다.


public class TempTestList
{
private List<int> m_ReferenceList;

public List<int>.Enumerator GetEnumerator() => m_ReferenceList.GetEnumerator();

public TempTestList(List<int> referenceList)
{
m_ReferenceList = referenceList;
}

}
private void TempTestMethod()
{
var list = new List<int>
{
1,
2
};
var testList = new TempTestList(list);
foreach (var t in testList)
{
//ignore
}
}


그럼 이제 List<int>를 IEnumerable<int> 로 캐스팅할 경우 어째서 foreach를 돌릴때 할당이 일어나는걸까?


왜냐하면 IEnumerable에 정의된 GetEnumerator의 반환값이 IEnumerator라는 인터페이스이기 때문이다.



List<T>의 경우 내부적으로 struct 타입의 Enumerator를 정의하고, 해당 타입을 반환하는 GetEnumerator 메서드가 존재한다.


그래서 평범하게 List<T>를 직접 foreach로 돌릴 경우, 컴파일러는 우선적으로 List<T>에서 정의한 GetEnumerator를 호출하고


반환값이 struct 여서 별도의 할당이 일어나지 않는다.



List<T> 를 IEnumerable<T> 로 캐스팅할 경우, 컴파일러는 List<T>가 아닌 IEnumerable<T>에 정의된 GetEnumerator를 호출한다.


List<T>의 경우 위에 말한 struct 타입이 반환값이 GetEnumerator 메서드가 존재하고, IEnumerable<T>의 구현은 해당 반환값을 IEnumerator<T>로 캐스팅해서 전달한다.


struct를 Interface로 캐스팅하면 당연하지만 박싱이 일어난다.


결과적으로 IEnumerable을 통한 foreach의 호출은 1번의 Heap 할당이 일어나게 된다.



여기까지 하면 예측할 수 있겠지만, Linq를 이용한 탐색은 무조건 IEnumerable 인터페이스를 반환하기 때문에


별도의 Linq용 인터페이스를 구현하고 할당이 일어나지 않도록 구현하지 않으면 Heap 할당이 발생한다.




그리고 여기까지 알아챈 사람이 있다면, Linq는 진짜 개나소나 다 쓰고 있기 때문에


딱히 이걸 최적화 한다고 해서 유의미한 영향이 없다는것도 눈치챘을 것이다....


결론은 그냥 평소에 쓰던데로 쓰면 된다.