C #에서 IDisposable과 소멸자를 사용하는 것의 차이점은 무엇입니까?


101

소멸자가 아닌 클래스에서 IDispose를 언제 구현합니까? 이 기사를 읽었 지만 여전히 요점이 누락되었습니다.

내 가정은 객체에 IDispose를 구현하면 가비지 수집기가 수행하기를 기다리는 대신 명시 적으로 '파괴'할 수 있다는 것입니다. 이 올바른지?

객체에 대해 항상 명시 적으로 Dispose를 호출해야 함을 의미합니까? 이에 대한 일반적인 예는 무엇입니까?


5
실제로 모든 Disposable 개체에 대해 Dispose를 호출해야합니다. using구성을 사용하여 쉽게 할 수 있습니다 .
— Luc Touraille

아, 말이 되네요. 나는 항상 왜 'using'문이 파일 스트림에 사용되는지 궁금했습니다. 나는 그것이 객체의 범위와 관련이 있다는 것을 알고 있지만 IDisposable 인터페이스와 관련된 맥락에 넣지 않았습니다.
— Jordan Parmer

5
기억해야 할 한 가지 중요한 점 은 종료자가 클래스의 관리되는 멤버에 액세스 해서는 안된다는 것 입니다. 이러한 멤버는 더 이상 유효한 참조가 아닐 수 있기 때문입니다.
— Dan Bryant

답변:


126

종료 자 (소멸자라고도 함)는 가비지 수집 (GC)의 일부입니다. GC는 주로 메모리 부족 (즉, 더 많은 공간이 필요함)의 결과로 발생하기 때문에 이것이 발생하는시기 (또는 발생하더라도) 불확실합니다. 종료자는 일반적으로 정리에만 사용됩니다. 관리되지 않는 리소스 관리되는 리소스에는 자체 수집 / 처리가 있기 때문입니다.

그 후 IDisposable 객체 를 결정적으로 정리하는 데 사용됩니다 . 객체의 메모리 (여전히 GC에 속해 있음)를 수집하지 않지만 예를 들어 파일, 데이터베이스 연결 등을 닫는 데 사용됩니다.

이에 대한 이전 주제가 많이 있습니다.

마지막으로, IDisposable객체가 종료 자를 갖는 것은 드문 일이 아닙니다 . 이 경우 Dispose()일반적으로를 호출합니다 GC.SuppressFinalize(this). 즉, GC는 종료자를 실행하지 않고 메모리를 버립니다 (훨씬 저렴). Dispose()객체 를 잊어 버린 경우에도 종료자가 계속 실행됩니다 .


감사! 그것은 완벽하게 이해됩니다. 큰 반응에 감사드립니다.
— Jordan Parmer

27
할 말이 하나 더 있습니다. 정말로 필요한 경우가 아니면 종료자를 클래스에 추가하지 마십시오. 종료 자 (소멸자)를 추가하면 GC가이를 호출해야하며 (빈 종료 자 일지라도)이를 호출하려면 객체는 항상 1 세대 가비지 수집에서 살아남습니다. 이것은 GC를 방해하고 느리게합니다. 그것은 Marc가 위 코드에서 SuppressFinalize를 호출하라고 말한 것입니다
— Kevin Jones

1
따라서 Finalize는 관리되지 않는 리소스를 해제하는 것입니다. 하지만 Dispose를 사용하여 관리되는 리소스와 관리되지 않는 리소스를 해제 할 수 있습니까?
— Dark_Knight

2
@Dark 예; (가) 관리 아래 6 단계 때문에 체인 프롬프트 정리를해야하는 관리되지 않는 하나가 될 수
— 마크 Gravell

1
@KevinJones 종료자가있는 객체는 1 세대가 아닌 0 세대에서 살아남는 것이 보장됩니다. .NET Performance라는 책에서 읽었습니다.
— David Klempfner

25

Finalize()메서드 의 역할은 가비지 수집시 .NET 개체가 관리되지 않는 리소스를 정리할 수 있도록하는 것 입니다. 그러나 데이터베이스 연결 또는 파일 처리기와 같은 개체는 가비지 수집에 의존하는 대신 가능한 한 빨리 해제해야합니다. 이를 위해 IDisposable인터페이스 를 구현 하고 Dispose()메서드 에서 리소스를 해제해야합니다 .


9

MSDN 에 대한 매우 좋은 설명이 있습니다 .

이 인터페이스의 주요 용도는 관리되지 않는 리소스 를 해제하는 것 입니다. 가비지 수집기 는 해당 개체가 더 이상 사용되지 않을 때 관리되는 개체에 할당 된 메모리를 자동으로 해제합니다 . 그러나 가비지 콜렉션이 언제 발생할지 예측할 수 없습니다 . 또한 가비지 수집기 는 창 핸들이나 열린 파일 및 스트림과 같은 관리되지 않는 리소스에 대한 지식이 없습니다 .

이 인터페이스의 Dispose 메서드를 사용하여 가비지 수집기와 함께 관리되지 않는 리소스 를 명시 적으로 해제 합니다. 개체 의 소비자는 개체가 더 이상 필요하지 않을 때이 메서드를 호출 할 수 있습니다.


1
이 설명의 한 가지 주요 약점은 MS가 관리되지 않는 리소스의 예를 제공하지만 실제로는 용어를 정의한 적이 없다는 것입니다. 관리되는 개체는 일반적으로 관리되는 코드 내에서만 사용할 수 있기 때문에 관리되지 않는 코드에 사용되는 것은 관리되지 않는 리소스라고 생각할 수 있지만 실제로는 그렇지 않습니다. 대부분의 비 관리 코드는 리소스를 사용하지 않으며 이벤트와 같은 일부 관리되지 않는 리소스는 관리되는 코드 유니버스에만 존재합니다.
— supercat

1
수명이 짧은 개체가 수명이 긴 개체의 이벤트를 구독하는 경우 (예 : 수명이 짧은 개체의 수명 내에 발생하는 모든 변경 사항에 대한 알림을 받도록 요청) 이러한 이벤트는 관리되지 않는 리소스로 간주되어야합니다. 이벤트를 구독 취소하면 수명이 짧은 개체의 수명이 수명이 긴 개체의 수명으로 연장됩니다. 수천 또는 수백만 개의 단기 객체가 이벤트를 구독했지만 구독을 취소하지 않고 중단 된 경우 메모리 또는 CPU 누수가 발생할 수 있습니다 (각 구독을 처리하는 데 필요한 시간이 증가하기 때문).
— supercat

1
관리 코드 내의 관리되지 않는 리소스와 관련된 또 다른 시나리오는 풀에서 개체 할당입니다. 특히 코드를 .NET Micro Framework에서 실행해야하는 경우 (가비지 수집기가 데스크톱 컴퓨터의 것보다 훨씬 덜 효율적 임) 코드가 예를 들어 구조 배열을 갖는 것이 도움이 될 수 있습니다. 각 구조는 "사용됨"으로 표시 될 수 있습니다. 또는 "무료". 할당 요청은 현재 "free"로 표시된 구조를 찾아서 "used"로 표시 한 다음 인덱스를 반환해야합니다. 릴리스 요청은 구조를 "무료"로 표시해야합니다. 할당 요청이 예를 들어 23을 반환하면 ...
— supercat

1
... 코드가 더 이상 항목 # 23이 필요하지 않다고 어레이 소유자에게 알리지 않으면 해당 어레이 슬롯은 다른 코드에서 사용할 수 없습니다. GC가 매우 효율적이기 때문에 이러한 배열 슬롯의 수동 할당은 데스크톱 코드에서 자주 사용되지 않지만 Micro Framework에서 실행되는 코드에서는 큰 차이를 만들 수 있습니다.
— supercat

8

C # 소멸자에 있어야하는 유일한 것은 다음 줄입니다.

Dispose(False);

그게 다야. 그 방법에는 다른 것이 없어야합니다.


3
이것은 Microsoft가 .NET 문서에서 제안한 디자인 패턴이지만 개체가 IDisposable이 아닌 경우에는 사용하지 마십시오. msdn.microsoft.com/en-us/library/fs2xkftw%28v=vs.110%29.aspx
— Zbyl 2013

1
Dispose 메서드가없는 종료자가있는 클래스를 제공해야하는 이유를 생각할 수 없습니다.
— Jonathan Allen

4

항상 전화해야하는지 여부에 대한 질문 Dispose은 일반적으로 열띤 논쟁입니다. .NET 커뮤니티에서 존경받는 개인의 흥미로운 관점을 보려면 이 블로그를 참조하십시오 .

개인적으로 전화 Dispose가 필수가 아니라는 Jeffrey Richter의 입장 은 엄청나게 약하다고 생각합니다. 그는 자신의 의견을 정당화하기 위해 두 가지 예를 제공합니다.

첫 번째 예에서 그는 DisposeWindows Forms 컨트롤을 호출 하는 것이 주류 시나리오에서 지루하고 불필요하다고 말합니다 . 그러나 그는 Dispose이러한 주류 시나리오에서 실제로 제어 컨테이너에 의해 자동으로 호출 된다는 점을 언급하지 않았습니다.

두 번째 예에서 그는 개발자가 IAsyncResult.WaitHandle속성이 대기 핸들을 느리게 초기화하여 불필요한 성능 저하를 초래한다는 사실을 깨닫지 않고 인스턴스 가 공격적으로 처리되어야 한다고 잘못 가정 할 수 있다고 말합니다 . 그러나이 예제의 문제점은 그 IAsyncResult자체가 IDisposable개체 처리에 대한 Microsoft의 자체 게시 지침을 준수하지 않는다는 것 입니다. 즉, 클래스가 IDisposable유형에 대한 참조를 보유하고 있으면 클래스 자체가 IDisposable. IAsyncResult그 규칙을 따를 경우 자체 Dispose방법으로 구성 구성원 중 어느 쪽을 폐기해야하는지 결정할 수 있습니다.

따라서 누군가가 더 설득력있는 주장을하지 않는 한, 저는 대부분 잘못된 디자인 선택으로 인해 발생하는 몇 가지 부가적인 사례가있을 것이라는 이해와 함께 "항상 Dispose를 호출"캠프에 머물 것입니다.


3

정말 간단합니다. 답변을 받았지만 다시 시도하지만 가능한 한 간단하게 유지하려고 노력할 것입니다.

일반적으로 소멸자는 사용하지 않아야합니다. .net이 실행되기를 원합니다. 가비지 수집주기 후에 만 ​​실행됩니다. 애플리케이션의 수명주기 동안에는 실제로 실행되지 않을 수 있습니다. 따라서 '반드시'실행해야하는 소멸자에 코드를 넣지 마십시오. 또한 클래스가 실행될 때 존재하는 기존 개체에 의존 할 수 없습니다 (소멸자가 실행되는 순서가 보장되지 않으므로 이미 정리되었을 수 있음).

정리가 필요한 리소스 (예 : 파일 및 그래픽 핸들)를 생성하는 개체가있을 때마다 IDisposible을 사용해야합니다. 사실, 많은 사람들은 소멸자에 넣은 모든 것이 위에 나열된 이유로 인해 IDisposable해야한다고 주장합니다.

대부분의 클래스는 종료자가 실행될 때 dispose를 호출하지만 이것은 단순히 안전 가드로서 존재하며 절대로 의존해서는 안됩니다. 작업이 끝나면 IDisposable을 구현하는 모든 것을 명시 적으로 폐기해야합니다. IDisposable을 구현하는 경우 종료 자에서 dispose를 호출해야합니다. 예제는 http://msdn.microsoft.com/en-us/library/system.idisposable.aspx 를 참조 하십시오 .


아니요, 가비지 수집기는 Dispose ()를 호출하지 않습니다. 종료 자만 호출합니다.
— Marc Gravell

수정했습니다. 클래스는 종료 자에서 dispose를 호출해야하지만 그럴 필요는 없습니다.
— DaEagle

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.