참치김밥은 최고의 한식이다

[Effective C# 2판] Item 8 : 익명 타입은 함수를 벗어나지 않게 사용하라 본문

책/C#

[Effective C# 2판] Item 8 : 익명 타입은 함수를 벗어나지 않게 사용하라

l__j__h 2024. 2. 21. 13:26
이 책의 Item 8장을 읽기 전, 알고 있으면 좋을 사전 지식 (직접 정리)
이름 기반 타이핑과 구조 기반 타이핑이 뭘까?

이름 기반 타이핑?
: 객체 간의 타입 호환성을 확인할 때, 타입의 ‘이름’을 사용하는 것.\
  예를 들어, class MyClass{ … }는 ‘MyClass’라는 이름으로 식별되고, 클래스 내의 int myInt1, int myInt2 등은 각각 ‘myInt1’과 ‘myInt2’라는 이름을 통해 개별 멤버로 인식된다. (내 이해를 담은 예시로, 실제 개념과 차이가 있을 경우 알려주시면 감사하겠습니다.)

구조 기반 타이핑?
: 이름 대신 형태를 확인하여 타입이 같은지 확인한다.
  예를 들면, 두 개의 정수를 가지는 모든 튜플은 모두 같은 타입으로 간주되며, 모두 System.ValueTuple<int, int>의 인스턴스이다.

 

 

튜플의 사용 예시

static (T sought, int index) FindFirstOccurrence<T>(IEnumerable<T> enumerable, T value){
	int index = 0;
	foreach(var element in enumerable){
		if(element == value)
			return (element, index); //value와 같은 값을 발견
		index += 1;//value가 아니면 index ++
}
	return (default(T), -1);//아예 발견 못 했으면 index(item2)를 -1로
}

 

enumerable을 돌다가, T value와 같은 값을 발견하면, value가 몇 번째 index에 있는지 반환해주는 제네릭 함수이다.

위처럼, 메서드의 반환 타입으로 튜플을 사용하기는 쉽다. 반면, 메서드의 반환 타입으로 익명 타입을 사용하기란 쉽지 않은데, 타입의 이름을 코드로 입력할 수 없기 때문이다. 하지만 이 경우에도 제네릭 메서드를 정의하고, 매개변수로 익명 타입의 객체를 취하도록 정의하면, 메서드의 매개변수 혹은 반환값으로 익명 타입을 사용할 수 있다.

 


 

익명타입의 사용 예시

static IEnumerable<T> FindValue<T>(IEnumerable<T> enumerable, T value){
	foreach(T element in enumerable){
		if(element.Equals(value))
			yield return element;
}
}

 

enumerable 안에서 value와 같은 element를 모두 반환한다.

 

위 메서드는 다음과 같이 익명 타입과 함께 사용할 수 있다.

IDictionary<int, string> numberDic = new Dictionary<int, string>(){
	{1, "one"},
	{2, "two"},
	{3, "three"},
	{4, "four"},
	{5, "five"}
};

List<int> numbers = new List<int>(){1,2,3,4,5,6};
var r = from n in numbers
				where n % 2 == 0
				select new {//이 줄에서 익명 함수 사용
					Number = n,
					Description = numberDic[n]
}; //여기서 r은 1~6 사이의 짝수(n&2==0)인 요소를 갖는 IEnumerable이 된다.

r = from n in FindValue(r, new {Number = 2, Description = "two"})
		select n;
//r에서 (Number = 2, Description = "two")인 짝만 찾아서 갖는다.

 

익명타입의 사용 예시 2 (Mapping 함수 만들기)

public static IEnumerable<TResult> Map<TSource, TResult>
								(this IEnumerable<TSource> source, Func<TSource, TResult> mapFunc){
//사용방법 : (IEnumerable변수).Map(TSource를 받
아 TResult를 내놓는 함수)
//반환값 : IEnumerable<TResult>
//매개변수 Func<TSource, TResult> mapFunc의 뜻은,
//매개변수로 받을 함수의 파라미터는 TSource이고 반환형식은 TResult임을 뜻한다.

	foreach(var s in source)
		yield return mapFunc(s);
//mapFunc의 반환값은 TResult이고, 그 값들을 yield return으로 계속 리턴시켜주고 있으므로 이 함수의 반환형이 IEnumerable<TResult>가 된다.
}

var sequence = (from x in Utilities.Generator(100, ()=>randomNumbers.NextDouble() * 100)
								let y = randomNumbers.NextDouble() * 100
								select new {x, y}).TakeWhile(point => point.x < 75);
//x, y에 랜덤한 값을 각각 할당하고 익명 함수를 만드는데, 이를 x의 값이 75 미만일 때 까지만 반복한다.

var scaled = sequence.Map(p=> new {
	x = p.x * 5;
	y = p.y * 5;
});

var distances = scaled.Map(p=>{//p는 TSource로, 즉 scaled의 요소 하나하나를 가리킨다.
//TSource인 scaled를 이용해서 어떤 형식을 결과로 내놓을지는 아래 익명 함수를 어떻게 쓰냐에 따라 자유롭다.
	x = p.x,
	y = p.y,
	distance = Math.Sqrt(p.x * p.x + p.y + p.y)
});

var filtered = from location in distances //범위 지정
									where location.distance < 500.0 //필터 지정
							select location;

 

코드를 작성하다보면, 이전에 작성한 메서드의 일부분이 다른 곳에서 재사용될 수 있다. 이럴 때는 재사용 가능한 코드를 따로 분리하여 제네릭 메서드로 작성한 후, 여러 곳에서 사용할 수 있도록 해야 한다.

단, 이러한 기법을 남용하지 않도록 주의해야 한다. 여러 알고리즘에서 자주 사용되는 핵심 타입이 있다면, 익명 타입으로 바꾸어서는 안 된다(오히려 비효율). 동일한 타입을 반복적으로 사용하고 있다면, 그 타입은 익명 타입이 아니라 구체적인 타입으로 변경해야 할 가능성이 크다.

다음 지침이 도움이 될 것이다. 만약 주요 알고리즘에서 동일한 익명 타입을 4번 이상 사용하고 있다면, 구체적인 타입으로 변경하기를 권한다. 더불어 코드가 길고 복잡한 람다 표현식을 사용하고 있고, 그 이유가 익명 타입을 사용하기 위해서라면, 굳이 익명 타입을 쓰지 말고, 구체적인 타입으로 바꾸는 게 낫다.

 

 

728x90