using System;
using var t = new Test();
using var tt = new Test
{
Member = default(int?) ?? throw new() // dispose 호출 불가!
};
ref struct Test
{
public Test()
{
Console.WriteLine("create!");
throw new(); // dispose 호출 불가!
}
public void Dispose() => Console.WriteLine("dispose!");
public int Member
{
get => field;
init => throw new(); // dispose 호출 불가!
}
}
일반적으로 IDisposable이 구현된 객체는, 생성되고 사용한 이후 반드시 Dispose()를 호출하여 폐기할 것을 요구합니다.
하지만 객체가 생성까지는 되었으나 아직 변수에 할당되지 않은 시점에서 예외가 발생한다면, 어차피 아무 접근도 불가능하니 Dispose()를 호출하는 것 또한 불가능 하게 됩니다.
생성자나 init { .. } 같이 해당 객체의 내부라면 당연히 예외가 발생하지 않도록 통제할 책임이 객체에게 있는것이지만, 외부 initializer 영역에서의 예외발생은 사용자에게 너무 손쉽게 해소불가능한 누수 유발 가능성을 제공합니다.
using System;
using System.Collections.Generic;
MyList<int>? list = null;
try
{
- list = new();
- list.Add(default(int?) ?? throw new());
+ list = [default(int?) ?? throw new()]; // IDE0028
}
finally
{
list?.Dispose();
}
class MyList<T> : List<T>, IDisposable
{
public MyList() => Console.WriteLine("create!");
public void Dispose() => Console.WriteLine("dispose!");
}
위 같은 코드에서는 dispose가 적절히 기능하던 기존 코드를 소스분석기의 권장사항을 따르는경우 dispose가 발생하지 않게 부적절하게 마이그레이션 되는걸로 보입니다.
객체가 생성은 됬으나, 추가 초기화 과정에서의 예외로 인해 적절히 Dispose하지 못하고 누수가 발생한다는 최악의 경우를 피하기 위해선 보통 어떻게 하나요?
- 초기화 블럭에서 예외발생이 안생기도록 사용자가 알아서 유의토록 한다.
- Disposable객체 생성에서 Initializer사용을 금지하는 분석기를 도입한다.
- Disposable객체 선언부터 Initializer를 활용할수 없도록 디자인한다.
- Disposable객체의 설계를
Open()Connect()등 정상적으로 변수에 할당된 이후에 가능한 호출을 통해 활성화하기 전까지는 굳이 폐기하지 않아도 폐기된것과 동일한 상태를 가지도록 한다.