ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Effective c# 간단 정리
    Read @ Book & E-Book 2017. 11. 12. 15:31

    목차별 내용요약.


    C# 언어요소

    1. 지역변수를 선언할 떄는 var를 사용하는 것이 낫다.

    var를 사용해서 타입 추론을 위임한 경우 할당문 오른쪽의 내용을 기반으로 타입을 결정한다.

    개발자를 대신하여 올바른 타입을 추론해준다. 반환값에 대해 잘못사용될 일이 없다.

    단, 내장된 숫자 타입과 var를 함께 사용할때는 주의하도록한다. 쉽게 넘어가기엔 정밀도에 문제가 발생할 수도 있다.

    그리고 대부분 변수의 이름을 통해서 그 역할이 명확하게 드러내도록 코드를 작성하는 것이 낫다.


    2. const보다는 readonly가 좋다

    const     : 컴파일타임 상수

    성능이 매우 중요하고 상수의 값이 절대로 바뀌지 않는 경우에만 제한적으로 사용한다. (런타임 상수보단 빠르다)

    내장된 숫자형, enum, 문자열, null에 대해서만 가능하다. 왜냐면 컴파일타임에 상수를 초기화해야하기 때문이다.

    리빌드 전까진 상수를 수정한 값이 변경이 되지 않는다.

    readonly : 런타임 상수

    유연하게 사용할때 고려한다. 위에 컴파일 타임상수에서 안되는것은 다된다고 보면된다.

    동일 클래스의 인스턴스라 하더라도 인스턴스별로 서로 다른 값을 가질 수 있다.

    멤버 초기화 구문과 생성자에서 초기화할 수 있다. 어떠한 타입과도 가능하다.


    3. 캐스트보다는 is, as가 좋다

    캐스트보다 as가 안정성이나 일관성있게 동일한 결과를 반환한다.


    4. string.Format()을 보간 문자열로 대체해라

    기존 string.Format()의 경우 직접 출력해보고 올바른 형태인지를 눈으로 확인하기 전까지는 코드를 제대로 작성했는지 쉽게 짐작하기 어렵다.

    그 이유는 포맷 문자열과 인자 리스트를 분리하여 전달하는 구조이기 때문이다. 인자 갯수 누락까지 실수 할 수 있다.

    보간 문자열을 사용하려면 문자열 앞에 $ 붙이고 문자열로 변경할 표현식은 { } 내에 둔다. 결과예측도 쉽고 직관적이다.


    5. 문화권별로 다른 문자열을 생성하려면  FormattableString을 사용하라

    6. nameof() 연산자를 적극 활용하라

    7. 델리게이트를 이용하여 콜백을 표현해라

    여러클래스가 상호통신을 수행해야할 떄 클래스 간의 결합도를 낮추고 싶다면 인터페이스보다 델리게이트를 사용하는 것이 좋다.

    멀티캐스트 델리게이트의 반환값은 멀티캐스트 체인에서 마지막으로 호출된 함수의 반환값이 되며, 다른 반환값은 모두 무시된다.

    델리게이트는 추가된 메서드의 리스트를 가지고 있으므로 이 리스트를 살펴서 직접 호출하는 방식으로 코드를 작성하면된다.

    GetInvocationList()를 이용하면 개별 메서드가 반환하는 경우를 확인할 수 있다.


    8. 이벤트 호출 시에는 null 조건 연산자를 사용하라

    이벤트에 이벤트 핸들러가 결합돼 있지 않다면 NullReferenceException예외가 발생된다.

    이벤트 핸들러가 결합되어 있는지를 확인하는 코드와 이벤트를 발생시키는 코드 사이에 race Condition이 발생할 가능성이 있다.

    이문제는 null 조건 연산자를 사용하면 해결 할 수있다. (연산자의 왼쪽을 평가하여 null이 아닌 경우에만 연산자 오른쪽의 표현식을 실행한다)

    ?연산자를 이용해서 이벤트를 발생시킬 떄는 이벤트 이름뒤에 ()를 붙여 호출 할 수 없으므로 Invoke() 메서드를 사용한다.


    9. 박싱과 언박싱을 최소화하라

    .NET Framework는 모든 타입의 최상위 타입을 참조 타입인 System.Object로 정의하고 있다. 그래서 박싱과 언박싱이라는 방법을 통해서 값과 참조라는

    서로 다른 타입을 이어준다.

    박싱과 언박싱은 성능에 좋지 않은 영향을 미친다. 대부분의 경우 .NET 2.0에 추가된 제네릭 클래스와 제네릭 메서드를 사용하면 박싱과 언박싱을

    피할 수 있다. 하지만 아직도 System.Object 타입의 객체를 요구하는 API가 있다. 여전히 박싱 언박싱 작업을 수행한다는 것이다.


    10. 베이스 클래스가 업그레이드된 경우에만  new 한정자를 사용하라


    .NET 리소스 관리

    11. .NET리소스 관리에 대한 이해

    가비지 수집기 : 메모리 누수, 댕글링 포인터, 초기화되지 않는 포인터, 여타의 메모리 관리 문제를 개발자가 직접 다루지 않도록 자동화한것이다.

    가비지 수집기는 COM의 경우처럼 개별 객체가 스스로 자신의 참조 여부나 횟수 등을 관리하도록 하지 않고, 응요 ㅇ프로그램의 최상위 객체로부터 개별 객체까지의 도달 가능 여부를 확인하도록 설계되어 있다. 도달 불가능이라면 가비지로 간주한다.

    가비지가 수행되면 힙에 대하여 콤팩트 작업을 수행한다. 콤팩트 작업이란 사용 중인 객체들을 한쪽으로 차곡차곡 옮겨서 조각난 가용 메모리를 단일의 큰 메모리 공간으로 만드는 과정이다.

    비관리 리소스의 경우 여전히 개발자가 관리해야한다.( 테이터 베이스연결, 시스템 객체 등)

    심지어 이벤트 핸들러나 델리게이트 등도 잘못 사용하면 이들이 참조하고 있는 객체들이 불필요하게 오랫동안 메모리에 남아있게 된다.

    손쉽게 관리할 수 있도록 finalizer와 IDisposable 인터페이스라는 두 가지를 제공해준다. finalizer는 단점이 많기 때문에 IDisposable을 구현하는것이 좋다.

    finalizer

    1) 가비지로 간주된 이후에도 긴 시간 동안 메모리를 점유하게 되며 사용자가 구현한 finalizer는 상당한 시간이 경과한 다음에야 비로소 수집기에 의해서 호출된다. 

    2) 그리고 정확히 어느 시점에 finalizer가 호출될지를 아무도 알 수가 없다.단지 객체가 가비지가 되면 언젠가는 호출되라라는 것만 알 수 있을 뿐이다.

    3) 심지어 finalizer를 수행하기 위한 추가적인 스레드가 필요하다. 성능도 좋지 않다. 


    12. 할당 구문보다 멤버 초기화 구문이 좋다

    보통 둘 이상의 생성자를 작성할때 생성나 내에서 멤버변수들의 초기화 구문을 작성하다보면 누락하는 경우가 있다. 이럴때 생성자 본문이 아니라

    멤버 초기화 구문을 사용하면 누락을 방지할 수 있다. 생성자를 갖기 않는 타입을 선언해도 가능하다.

    멤버 초기화 구문을 사용하지 않는 것이 좋은 경우

    1) null 또는 0으로 초기화. 기본 시스템 초기화 루틴은 코드를 실행하기전에 모든 값을 0으로 설정해주기 때문이다.

    2) 동일한 객체를 반복해서 초기화하는 경우다.

    : 멤버 초기화 + 생성자에서 초기화하는 경우

    3 반드시 예외 처리가 필요한 초기화시

    : 멤버 초기화 구문일 경우 클래스 내부에서 복구를 시도할 수 없다


    13. 정적 클래스 멤버를 올바르게 초기화하라

    정적 멤버 변수를 포함하는 타입이 있다면 인스턴스를 생성하기전에 반드시 초기화해야한다. 

    정적 멤버 초기화, 정적 생성자 2가지가 제공한다.

    초기화하기 위해 인스턴스 생성자나 전용의 private 메서드를 사용해서는 안된다.

    매우 복잡하거나 상당한 자원이 소비하는 경우 Lazy<T>를 사용하는것이 좋다.

    간단한 초기화 : 멤버 초기화, 다시 복잡한 초기화 : 정적 생성자 초기화

    베이스 클래스의 정적 생성자보다 멤버초기화가 먼저 호출된다.


    14. 초기화 코드가 중복되는 것을 최소화하라

    여러 생성자내에서 동일한 코드를 반복적으로 사용한다면 공용으로 사용할 수 있는 생성자를 작성하는 편이 낫다.

    별도의 함수로 관리하거나 그런것은 비효율적이다.(readonly의 초기화 문제 등)

    코드중복도 피하며 효율적이다. 내부적으로 최적화를 해준다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class MyClass
    {
        private List<int> coll;
        private string name;
        public MyClass() : this(0string.Empty){}
        public MyClass(int count = 0string name = "")
        {
            coll = (count > 0) ? new List<int>(count) : new List<int>();
            this.name = name;
        }
    }
    cs

    new() 제약 조건을 명시한 제네릭 클래스와 함께 사용해야 할 경우에는 기본값을 가지는 매개변수를 취하는 생성자만으론 안된다.

    반드시 매개변수가 없는 생성자를 구현해야한다.

    단점도 있다. 여러개의 생성자를 만드는 대신 기본값을 갖는 매개변수를 취하는 생성자를 작성하게되면 이 타입을 사용하는 코드와의 결합도가

    높아지는 단점도 있다.

    인스턴스가 생성되는 순서


    15. 불필요한 객체를 만들지 말라

    가비지 수집기가 과도하게 동작하지 않도록 주의해야한다.

    모든 참조 타입의 객체는 그것이 설사 지역변수라 하더라도 동적으로 메모리를 할당한다. 

    이렇게 할당된 객체는 이 객체를 참조하는 상위객체가 삭제되면 가비지가 된다. 지역변수의 경우 그 변수를 선언한 메서드를 벗어나는 순간 가비지가 된다.

    1. 자주 할당되는 참조객체의 경우 지역변수에서 멤버변수로 변경한다. (또는 정적 멤버변수로 선언한다)

    IDisposable인터페이스를 구현한 타입의 객체를 지역변수에서 멤버변수로 변경하면 이 클래스도 반드시 IDisposable을 구현해야한다.

    단. 정적이라면 필요이상으로 오랫동안 메모리에 남아있고 Dispose메서드를 호출해야할 시점을 결정할 수 없다.

    string msg = "hello";

    msg += "name; 

    이런형식의 string은 가비지가 된다. 그래서 string.Format를 사용해야한다.


    16. 생성자 내에서는 절대로 가상함수를 호출하지 마라

    17. 표준 Dispose패턴을 구현하라

    IDisposable 인터페이스 : 리소스를 삭제할 수 있는 기능을 안정적으로 제공한다

    finalizer : 비관리 리소스를 명시적으로 정리해야 한다는 사실을 잊거나 인지하지 못한경우에 사용

    상속계통산 최상위의 베이스 클래스는 다음과 같은 작업을 해야한다.

    1. 리소스를 정리하기 위해 IDisposable 인터페이스 구현

    2. 멤버 필드로 비관리 리소스를 포함하는 경우에 한해 방어적으로 동작할 수 있도록 finalizer를 추가

    3. Dispose와 finalizer(존재하는 경우) 실제 리소스 정리작업을 수행하는 다른 가상 메서드에 작업을 위임하도록 작성(파생클래스에서 고유의 리소스 정리가 필요할때 재정의 하기 위함)

    파생클래스에서 해야하는 작업은 아래와같다

    1. 고유의 리소스 정리 작업을 한다면 가상메서드의 재정의를 통해 한다.

    2. 멤버 필드로 비관리 리소스를 포함한 경우에만 finalizer 추가

    3. 베이스 클래스에서 정의하고 있는 가상함수 반드시 호출


    사용자가 Dispose() 메서드를 항상 올바르게 호출할 것이라고 가정할 수 없는 관계로 finalizer가 나온것이다.

    둘다 안하게 되면 리소스 누수 발생한다. 어떤 경우에도 비관리 리소스가 누수되지 않고 올바르게 정리될 것임을 보장하는 유일한 방법이 finalizer이다


    'Read @ Book & E-Book' 카테고리의 다른 글

    최근에 읽고있는 책..  (0) 2014.06.28
Designed by Tistory.