Effective C# Chapter 4
아이템 29 컬렉션 반환보다 이터레이터를 반환하는 것이 낫다.
//0~ int 최대까지 모든 값 반환
//시퀀스로 전체 값을 받음
var allNumbers = Enumerable.Range(0, int.MaxValue);
//yield return으로 값을 반환하고
//foreach로 받게 되면 필요한 만큼만 받을 수 있음.
private static IEnumerable<char> GenerateAlphabetSubsetImpl(char first, char last)
{
var letter = first;
while(letter <= last)
{
yield return letter;
letter++;
}
}
첫 번째 구문에서 allNumbers를 어떻게 조작하든 전체 값을 다 받는다
하지만 아래처럼 작성하면 필요한 만큼만(first ~ last까지) 받을 수 있다.
아이템 30 루프보다 쿼리 구문이 낫다
//0~100까지 있는 점들중 x, y값의 합이 100이하인 점들 구하기
//코드가 까다로워 실수할 여지가 많다. 객체.요소를 여러번에 걸쳐서 선언필요
private static IEnumerable<Tuple<int, int>> ProduceIndices3()
{
var storage = new List<Tuple<int, int>>();
for(var x = 0; x < 100; x++)
for(var x = 0; x < 100; x++)
if(x + y < 100)
storage.Add(Typle.Create(x, y));
storage.Sort((point1, point2) =>
(point2.Item1 * point2.Item1 + point2.Item2 * point2.Item2).CompareTo(
point1.Item1 * point1.Item1 + point1.Item2 * point2.Item2));
}
//linq문 한번으로 간결하게 작성
private static IEnumerable<Tuple<int, int>> QueryIndices3()
{
return from x in Enumerable.Range(0, 100)
from y in Enumerable.Range(0, 100)
where x + y < 100
orderby (x * y + y * y) descending
select Typle.Create(x, y);
}
경우에 따라서도 가독성 차이가 있고, 성능도 루프문보다 느린 경우가 있으므로 성능 측정을 해보는 것이 좋음
아이템 31 시퀀스에 사용할 수 있는 조합 가능한 API를 작성하라
yield return 반환, foreach문으로 호출해주기
- 개별요소에 대해 지연 평가/수행이 가능
public static void Unique(IEnumerable<int> nums)
{
var uniqueVals = new HashSet<int>();
foreach(var num in nums)
{
if(!uniqueVals.Contains(num))
{
uniquevVals.Add(num);
WriteLine(num);
}
}
}
//위 함수를 아래로 변경
public static IEnumerable<T> Unique<T>(IEnumerable<T> sequence)
{
var uniqueVals = new HashSet<T>();
foreach(T item in sequence)
{
if(!uniqueVals.Contains(item))
{
uniqueVals.Add(item);
yield return item;
}
}
}
//한번에 여러 조건을 엮어 foreach문을 순회할수 있음
foreach(var num in Squiare(Unique(nums))
WriteLine("Number returned from Unique : {0}, num);
위의 경우 foreach에 대응하지 않고 각자의 결과 값만 받아서 결합하므로 중복 순회가 일어날 수 있음.
위의 경우 단 한번만 순회 하므로
요소 하나를 모든조건 체크후 거름
아이템 32 Action, Predicate, Function ↔ 순회 방식을 분리 하라
유용하게 사용할 수 있는 예시들
필터만 변경하거나, 실행 함수만을 매개변수로 보내기 때문에 활용도가 높음.
//Predicate<T>조건 필터를 매개변수로 받아 결과 시퀀스를 반환
public static IEnumerable<T> Where<T>(IEnumerable<T> sequence, Predicate<T> filterFunc) //Predicate 필터로서 사용 예시
{
//sequence, filterFunc null체크 생략
foreach(T item in sequence)
if(filterFunc(item))
yield return item;
}
//Func 사용예시
public static IEnumerable<T> Select<T>(IEnumerable<T> sequence, Func<T, T> method)
{
foreach(T element in sequence)
yield return method(element);
}
foreach(int i in Select(myInts, value => value * value))
WriteLine(t);
foreach문과는 분리되어 구현
- 조건 필터로써 Predicate<T> filderFunc를 받음
- 조건에 맞는 경우 사용하기 위해 Func<T, T> method 받음
- myInts시퀀스를 보내고 이들값의 제곱 시퀀스를 반환받음
아이템 34 함수를 매개변수로 사용하여 결합도를 낮추라
위에서 보였다 싶이 Func, Action등으로 보내고 호출해주면 부른쪽(매개변수로 함수를 넘겨준 쪽)과 불린쪽(실행된 함수)의 결합이 생기지 않는다.
아이템 35 확장 메서드는 절대 오버로드 하지마라
네임스페이스를 나눠 확장 메서드를 정의하는 경우
using을 바꿔서 출력방향이 바뀌고, 같은 이름이 있는경우 모호한 참조로 컴파일 에러
*확장 메서드를 추가할 때 고려사항
- 추가하려는 기능이 타입 내에 포함되는 것이 적절한 경우에만 사용
- 타입. 으로 호출했을때 자연스러운지
확장 메서드의 오버로딩을 해야된다면 대신 함수이름을 바꾸고 static 메서드로 호출하는 것을 고려
아이템 36 쿼리 표현식과 메서드 호출 구문이 어떻게 대응되는지 이해하라
//예제 1)
var allNumbers = from n in numbers select n;
//대응되는 메소드
var allNumbers = numbers.Select(n => n);
var smallNumbers = from n in numbers
where n < 5
select n * n;
//대응되는 메소드
var smallNumbers = numbers.Select(n => new { Number = n, Square = n * n});
//예제 2)
//결과가 명료하지 않은 경우들
var people = from e in employees
where e.Age > 30
orderby e.LastName, e.FirstName, e.Age
select e;
//1차적으로 정렬한 결과에서 다음 기준을 씌우는 것이므로
//OrderBy로 모두 호출되는 것이 아닌 ThenBy로 호출된다.
//대응되는 메소드
var people = employees.Where(e => e.Age > 30).
OrderBy(e => e.LastName).
ThenBy(e => e.FirstName).
ThenBy(e => e.Age);
//예제 3)
var results = from e in employees
group e by e.Department into d
select new
{
Department = d.Key,
Size = d.Count()
};
//group e by e.Department into d
//위에서 이부분ㅇs from e in employees group e by e.Department로 변경된다.
//그 후 결과 대응되는 메소드
var results = employees.GroupBy(e => e.Department)
.Select(d => new { department = d.Key, Size = d.Count()});
책에 예제가 쭉 더 있는데.. 얼추 알만한 내용들이고, 그렇게 까지 어렵게 추측할 만한 내용은 거의 없었음
아이템 37 쿼리를 사용할 때는 즉시 평가보다 지연 평가가 낫다
지연 평가 : 결과를 얻기 위한 절차만을 정의하고 쿼리의 결과를 이용해 순회를 해야만 결과가 생성되는 방식
즉시 평가 : 일반 변수를 사용하는 것 처럼 즉각적으로 값을 얻어오는 방식
지연 평가의 장점
foreach, yield return으로 IEnumerable<TResult> 형으로 반환하는 예제(생략)
var sequence1 = Generate(10, () => DateTime.Now);
var sequence2 = from value in sequence1
select value.ToUniversalTime();
//sequence1에서 Generate는 지연 평가 함수
//seuquence2에서 필요한 값을 가져올때 미리 바꿔놓은 값을 가져오는게 아니라,
//필요한 시점에 sequence1에서 ToUniversalTime()을 실행해 값을 얻는다.
아이템 40 지연 수행과 즉시 수행을 구분하라
일반적으로 필요할 때만 해당 기능을 수행하는 지연 수행이 쿼리 불필요한 부하를 줄이는데 더 도움이 됨.
당연히, 필요할 때 빠르게 가져와야하는 경우 즉시 수행도 사용해야 함
아이템 41 값 비싼 리소스를 캡처하지 말라
클로저는 클로저에 바인딩된 변수를 포함하는 객체를 생성
바인딩된 변수의 수명이 길어지면 문제가 발생할 수 있음
지역 변수는 블럭을 나가면 해제되지만, 클로저에 캡쳐된 변수는 이 변수가 참조하는 객체의 수명이 늘어나게 된다.
해당 변수를 사용하는 델리게이트가 가비지화 될 때까지 그 변수는 해제되지 않음.
private class Closure
{
public int generatedCOunter;
public int generatorFunc() => generatedCounter++;
}
//사용 예
var c= new Clousure();
c.generatedCounter = 0;
var sequence = Extensions.Generate(30, new Func<int>(c.generatorFunc);
//원래라면 블럭을 벗어나는 순간 지역변수 c는 할당해제 되어야하지만
//Closure내부에서
//c.generatorFunc를 참조하고 있으므로 해당 변수는 해제되지 않는다.
//위 상황은 결국 지연평가인 sequence의 동작이 마무리되야 해당 객체가 할당 해제 된다. (한참 남아 있을 수 있음)
아이템 43 쿼리 결과의 의미를 명확히 강제하고, SIngle()과 First()를 사용하라
//단 하나의 요소만 반환하도록 강제하고 싶다면 Single(), First() 사용
var answer = (from p in somePeople
where p.FirstName == "Bill"
select p).Single();
//하나 이상 반환할 경우 InvalidOperationException 발생
//..개인적으로는 컴파일 에러가 아니라 예외기 때문에.. 강제한다고 하기 보단 귀찮은 일이 하나 추가된게 아닌가 싶다.
//SingleOrDefault()를 쓰면 null도 가능
//First()는 조건에 맞는 첫 번째 값 반환
var answer = (from p in somePeople
where p.FirstName == "Bill"
select p).First();
//조건에 맞는 것중 3번째 가져오기
var answer = (from p in somePeople
where p.FirstName == "Bill"
select p).Skip(2).First();
아이템 44 바인딩된 변수는 수정하지 말라
var index = 0;
Func<IEnumerable<int>> sequence = () => Utilities.Generate(30, () => index++);
index = 20;
foreach(int n in sequence())
WriteLine(n);//20~50까치 출력
index = 100;
foreach(int n in sequence())
WriteLine(n);//100부터 130까지 출력.
C# 컴파일러는 쿼리표현식, 람다 표현식 모두 정적 델리게이트나 인스턴스 델리게이트, 클로저로 변환한다.
*Closure 객체에 대한 내용
https://www.csharpstudy.com/DevNote/Article/26
나한테는 해당 안된 내용
쓸 일 없을 듯
아이템 33 필요한 시점에 필요한 요소를 생성하라
BindlingList<int> 같은 메모리 공간에서 참조만 함
양방향 데이터 바인딩
var data = new BindingList<int>(CreateSequence(100, 0, 5).ToList());
//익명 델리게이트
var sequence = CreateSequence(10000, 0, 7).TakeWhile(delegate (int num) { return num < 1000;}); // 항상 10000개가 생성
//람다 표기법
var sequence = CreateSequence(10000, 0, 7).TakeWhile((num) => num < 1000);
아이템 39 function과 action 내에서는 예외가 발생하지 않도록 하라
별얘기 없음 원래도 try catch 해준적이 없기 때문에 그닥 ..
아이템 38 메서드보다 람다 표현식이 낫다
아이템31 : 시퀀스에 사용할 수 있는 조합 가능한 API를 작성하라에서
필터로 쓴 Predicate<>, 조건이 맞으면 실행한 Action<> Func<>와 같이 람다식에서 간결하게 사용할 수 있도록 하기 위함
그냥 아이템31 또보는게 나을 듯
아이템 42 IENumerable<T> 데이터 소스와 IQueryable<T> 데이터 소스를 구분하라
역시 앞에서 얼추 알았지만, 클라에서 구분할 일은 잘 없음
Chapter 5에 있는 예외처리는 Api 제작자의 입장에서 쓰여진 내용들이 많고
클라 개발 전반에서 유익한 내용은 없을 것으로 보여 생략