1.01 ^ 365 = 37.78

[Effective C#] 2장 .NET 리소스 관리 ( ITEM 11 ~ 17 ) 본문

프로그래밍/Effective C#

[Effective C#] 2장 .NET 리소스 관리 ( ITEM 11 ~ 17 )

Youngchangoon 2019. 5. 3. 23:27

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

  • 힙에 관한 메모리 관리는 가비지 콜렉터가 완전히 책임진다. 그러나 그 외의 비관리 리소스는 개발자가 관리해야함.

비관리 리소스finalizerIDisposable인터페이스라는 두가지 메커니즘 제공

  • [위험] finalizer: finalizer를 포함하고 있는 객체를 가비지로 판단한 경우, 이 객체에 대한 참조를 다른 큐에 삽입하여 나중에 finalizer를 호출될 수 있도록 사전 준비만 수행.

가비지 콜렉터의 세대 개념

  • 0세대: 수집 절차 이후 생성된 객체들
  • 1세대: 수집 절차에서 살아남은 객체들
  • 2세대: 두번 혹은 그 이상의 수집 절차에서 살아남은 객체들

수집 절차

  • 기본적으로 0세대 객체만 검사
  • 1세대는 대략 10번에 한 번 검사
  • 2세대는 대략 100번에 한 번 검사

결론

  • 비관리 리소스를 해제하는 좋은 방법은 IDisposable을 사용하는 것이다.

ITEM 12: 할당 구문보다 맴버 초기화 구문이 좋다.

특징

  • 새로운 생성자를 추가하더라도 별도로 초기화할 필요가 없다.
  • 베이스 클래스 생성자가 호출되기 전에 멤버에 대한 초기화가 이루어진다.

사용법

1
2
3
4
public class MyClass
{
    private List<string> labels = new List<string>();
}
cs

예외사항

  • 만약 클래스내의 객체를 생성하는 방식이 여러가지라면 두 번 할당하는 꼴이 되므로 X
  • C#은 저수준에서 모든 값을 0으로 초기화하기 때문에 0이나 null로 초기화는 X
  • 예외 처리가 필요하다면 반드시 생성자 내에서 처리..!

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

  • 정적 멤버 변수를 포함하는 타입이 있다면 인스턴스를 생성하기 전에 반드시 정적 멤버 변수를 초기화 해야한다.
    • 정적 멤버 초기화 구문
    • 정적 생성자 : 타입 내에 정의된 모든 메서드, 변수, 속성에 최초로 접근하기 전에 자동으로 호출됨

정적 생성자를 이용하는 사례 ( ex: Singleton )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 정적 멤버를 간단히 초기화 하는 경우
public class MySingleton
{
    private static readonly MySingleton theOneAndOnly = new MySingleton();
 
    public static MySingleton TheOnly
    {
            return theOneAndOnly;
    }
 
    private MySingleton()
    {
    }
}
 
// 정적 생성자 버전 ( 초기화 과정이 더 복잡한 경우 )
public class MySingleton2
{
    private static readonly MySingleton2 theOneAndOnly;
 
    static MySingleton2()
    {
        theOneAndOnly = new MySingleton2();
    }
    public static MySingleton2 TheOnly
    {
        get
        {
            return theOneAndOnly;
        }
    }
 
    private MySingleton2()
    {
    }
}
cs


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

  • 공용생성자를 이용하라

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyClass
{
    private List<int> coll;
    private string name;
    private float scale;
 
    public MyClass() :
        this(0.2f, 0string.Empty)
    {
    }
 
     public MyClass(float scale, int initalCount = 0string name = "")
    {
        coll = (initalCount > 0) ?
            new List<int>(initalCount) :
            new List<int>();
        this.name = name;
        this.scale = scale;
    }
}
cs
  • 초기화 과정 일부를 공용생성자에 위임할 수 있다.

  • 어떠한 경우에도 제한 없이 new MyClass()를 사용할 수 있다.

  • 베이스 클래스의 생성자가 반복적으로 호출 되는것도 막아줌

인스턴스가 생성될때 수행되는 과정

< 클래스 자체에 대한 초기화 작업은 한번만! >

  1. 정적 변수의 저장공간을 0으로 초기화
  2. 정적 변수에 대한 초기화 구문
  3. 베이스 클래스의 정적 생성자 수행
  4. 정적 생성자 수행

< 동일한 타입으로 추가 인스턴스 생성할경우 여기서 부터 반복 >

  1. 인스턴스 변수의 저장공간을 0으로 초기화
  2. 인스턴스 변수에 대한 초기화 구문 수행
  3. 적절한 베이스 클래스의 인스턴스 생성자 수행
  4. 인스턴스 생성자 수행

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

  1. 자주 객체를 생성하는 지역변수멤버변수로 변경하는 방법 ( 참조 타입만 )

  2. 자주 사용되는 참조타입의 인스턴스를 정적 멤버 변수로 선언하는 방법

    • 단점1: 경우에 따라서 메모리상에 필요이상으로 남을수 있음
    • 단점2: Dispose() 메서드를 호출해야 할 시점을 결정할 수 없음.
    • 사용법
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      private static Brush blackBrush;
       
      public static Brush Black
      {
          get
          {
                  if(blackBrush == null)
                      blackBrush = new SolidBrush(Color.Black);
                  return blackBrush;
          }
      }
      cs
  3. 변경 불가능한 타입(Immutable) 부분 ( ex_ string.. )

  • string은 변경할 수 있는것처럼 보이나 새로운 stirng 객체가 생성됨

→ string.Format, 문자열 보간, StringBuilder등 사용하기

  • 결론: 변경 불가능한 타입을 작성하는 경우 StringBuilder와 같은 Builder 기능을 함께 제공하는것을 고려해보기.

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

  • 객체가 완전히 생성되기전 가상 함수를 호출하면 이상 동작이 발생함.
  • 베이스 클래스의 생성자 내에서 가상 함후를 호출하면 파생 클래스가 가상 함수를 어떻게 구현했는지에 따라 매우 민감하게 동작하게됨.
  • FxCop나 정적 코드 분석기등을 통해 이런 코드 패턴을 쉽게 발견 할 수 있음.

ITEM 17: 표준 Dispose 패턴을 구현하라

  • 비관리 리소스를 포함하고 있다면 무조건 finalizer를 구현하라.
  • 반대로 비관리 리소스가 없을 경우, finalizer를 절대로 추가하지 마라.
  • Dispose 메서드 내에서는 리소스 정리작업만 수행하라.

표준 Dispose 패턴 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class MyResourceHog : IDisposable
{
    private bool alreadyDisposed = false;
     ~MyResourceHog()
    {
        // Dispose가 호출 되지 않은 상황인 경우에 finalizer 로 들어옴
        // 비관리 리소스가 포함된 경우나
        // IDisposable을 구현한 다른 타입을 포함해야 할 경우에만 구현.
        Dispose(false);
    }
    public void Dispose()
    {
        Dispose(true);
 
        // GC가 돌때 fionalizer를 호출하지 않도록
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool isDisposing)
    {
        if (alreadyDisposed)
            return;
        if(isDisposing)
        {
            // 관리 리소스 정리
        }
 
        // 비관리 리소스 정리
 
        // dispose Flag
        alreadyDisposed = true;
    }
 
    public void ExampleMethod()
    {
        if(alreadyDisposed)
            throw new ObjectDisposedException("MyResourceHog"
                "Called Example Method on Disposed object");
    }
}
 
public class DerivedResourceHog : MyResourceHog
{
    private bool disposed = false;
    protected override void Dispose(bool isDisposing)
    {
        if (disposed)
            return;
 
        if(isDisposing)
        {
            // 관리 리소스 정리
        }
 
        // 비관리 리소스 정리
            
        // 베이스 클래스가 자신의 리소스 정리할 수 있도록
        base.Dispose(isDisposing);
 
        disposed = true;
    }
}
cs