본문 바로가기
책/Effective C#

Effective C# - Item 21 타입 매개 변수가 IDisposable을 구현한 경우를 대비하여 제네릭 클래스를 작성하라

by 코딩하는 돼징 2023. 12. 10.
반응형

제약 조건은 두 가지 역할을 한다.

1. 런타임 오류가 발생할 가능성이 있는 부분을 컴파일 타임 오류로 대체

2. 타입 매개변수로 사용할 수 있는 타입을 명확히 규정하여 사용자에게 도움

 

제약 조건은 해당 타입이 특정 인터페이스를 구현하는 등의 특성을 지정할 수 있지만 해당 타입이 특정 작업을 수행하도록 강제하지는 않는다. 예를 들어 IDisposable을 구현하는 타입이라면 특별한 추가 작업이 필요하다고 설명했지만 제약 조건 자체는 해당 타입이 IDisposable을 구현하는지 여부만 확인할 뿐 실제로 IDisposable에서 요구하는 작업을 강제하지는 않는디.


제네릭 메서드 내에서 타입 매개 변수로 주어지는 타입을 이용하여 인스턴스를 생성하는 경우 발생

T가 IDisposable을 구현한 타입을 경우 리소스 누수가 발생할 수 있다. 따라서 T타입으로 지역변수를 생성할 때마다 T가 Disposable을 구현하고 있는지 확인해아 하며 만약 IDisposable을 구현하고 있다면 추가적인 처리를 해야 한다.

public interface IEngine()
{
    void DoWork();
}
public class EngineDriverOne<T> where T : IEngine, new()
{
    public void GetThingsDone()
    {
        T driver = new T();
        driver.DoWork();
    }
}

해결 방법

01 using문 사용

만약 지역변수 driver값이 null인 경우 Dispose()가 호출되지 않고, IDisposable을 구현했다면 using블록을 종료할때 Dispose()메서드가 호출된다.

public void GetThingsDone()
{
    T driver = new T();
    using(driver as IDisposable)
    {
        driver.DoWork();
    }
}

02 Dispose호출책임을 제네릭 클래스 외부로 전담

객체의 소유권을 제네릭 클래스 외부로 옮긴다면 new() 제약 조건을 제거할 수 있다. 즉 생성된 객체를 외부에서 주입하여 IDisposable을 제거한다.

public sealed class EngineDriver<T> where T : IEngine
{
    // null로 초기화
    private T driver;
    
    public EngineDriver(T driver)
    {
        this.driver = driver;
    }
    
    public void GetThingsDone()
    {
        driver.DoWork();
    }
}

이렇게 하면 외부에서 생성된 객체의 소유권을 가지게 되어 IDisposable 관리가 용이다.


결론

제네릭 클래스의 타입 매개변수로 객체를 생성하는 경우 이 타입이 IDisposable을 구현하고 있는지 확인해야한다. 항상 방어적으로 코드를 작성하고 객체가 삭제될때 리소스가 누수되지 않도록 주의해야한다.

 

 

 

 

본 게시글은 Effective C#을 읽고 정리하였습니다.

 

반응형

댓글