이 간단한 수업으로 시작하십시오 …
다음과 같은 간단한 클래스 세트가 있다고 가정 해 봅시다.
class Bus
{
Driver busDriver = new Driver();
}
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
class Shoe
{
Shoelace lace = new Shoelace();
}
class Shoelace
{
bool tied = false;
}
A Bus
는 Driver
, Driver
두 개 는 Shoe
각각 Shoe
을가 Shoelace
집니다. 모두 매우 바보입니다.
신발 끈에 IDisposable 객체 추가
나중에 나는 일부 작업 Shoelace
이 멀티 스레드 될 수 있다고 결정 하므로 EventWaitHandle
스레드가 통신 할 수 있도록 추가합니다 . 그래서 Shoelace
지금은 다음과 같습니다 :
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
신발 끈에 IDisposable 구현
그러나 이제 Microsoft의 FxCop 은 다음과 같이 불평 할 것 입니다.
좋아, 내가 구현 IDisposable
에 Shoelace
내 깔끔한 작은 클래스는이 끔찍한 엉망이된다 :
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Shoelace()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
}
// No unmanaged resources to release otherwise they'd go here.
}
disposed = true;
}
}
또는 (해설자가 지적한 바와 같이) Shoelace
자체에는 관리되지 않는 리소스가 없기 때문에 Dispose(bool)
and Destructor가 없어도 더 간단한 dispose 구현을 사용할 수 있습니다 .
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
public void Dispose()
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
GC.SuppressFinalize(this);
}
}
IDisposable이 퍼짐에 따라 공포에서 시청
맞습니다. 그러나 이제 FxCop은을 Shoe
생성 한다고 불평 Shoelace
하므로 너무 커야 Shoe
합니다 IDisposable
.
그리고 Driver
생성은 Shoe
그렇게 Driver
해야합니다 IDisposable
. 그리고 Bus
생성은 Driver
그렇게 Bus
해야합니다 IDisposable
등등합니다.
갑자기 내 작은 변경으로 Shoelace
인해 많은 작업이 발생했으며 상사는 Bus
변경을 위해 왜 체크 아웃해야하는지 궁금 합니다 Shoelace
.
질문
이러한 확산을 방지 IDisposable
하면서도 관리되지 않는 개체가 올바르게 처리되도록 하려면 어떻게해야 합니까?
답변
IDisposable의 확산을 실제로 “예방”할 수는 없습니다. 일부 클래스는와 같이 처리해야 AutoResetEvent
하며 가장 효율적인 방법은 종료 자 Dispose()
오버 헤드를 피하기 위해 메소드 에서 수행하는 것입니다 . 그러나이 방법은 어떻게 든 예제에서와 같이 IDisposable을 캡슐화하거나 포함하는 클래스에서이를 처리해야하므로 처분해야합니다. 등을 피하는 유일한 방법은 다음과 같습니다.
- 가능하면 IDisposable 클래스 사용을 피하고, 단일 장소에서 이벤트를 잠 그거나 기다리거나, 고가의 자원을 단일 장소에 보관하십시오
- 필요할 때만 작성하고 바로 다음에 배치하십시오 (
using
패턴)
경우에 따라 IDisposable은 선택적 사례를 지원하므로 무시할 수 있습니다. 예를 들어, WaitHandle은 명명 된 Mutex를 지원하기 위해 IDisposable을 구현합니다. 이름을 사용하지 않으면 Dispose 메서드는 아무 작업도 수행하지 않습니다. MemoryStream은 또 다른 예이며, 시스템 리소스를 사용하지 않으며 Dispose 구현도 아무 것도 수행하지 않습니다. 관리되지 않는 리소스의 사용 여부에 대해 신중하게 생각하면 도움이 될 수 있습니다. 따라서 .net 라이브러리에 사용 가능한 소스를 검사하거나 디 컴파일러를 사용할 수 있습니다.
답변
정확성 측면에서, 부모 개체가 이제 일회용 개체 여야하고 자식 개체를 소유하는 경우 개체 관계를 통해 IDisposable의 확산을 막을 수 없습니다. 이 상황에서는 FxCop이 정확하며 부모는 ID를 지정할 수 있어야합니다.
객체 계층의 리프 클래스에 IDisposable을 추가하지 마십시오. 이것은 항상 쉬운 일이 아니지만 재미있는 운동입니다. 논리적 관점에서 ShoeLace를 일회용으로 사용할 필요는 없습니다. 여기에 WaitHandle을 추가하는 대신 사용되는 지점에서 ShoeLace와 WaitHandle 간의 연관을 추가 할 수도 있습니다. 가장 간단한 방법은 Dictionary 인스턴스를 통하는 것입니다.
WaitHandle이 실제로 사용되는 지점에서 맵을 통해 WaitHandle을 느슨한 연관으로 이동할 수 있으면이 체인을 끊을 수 있습니다.
답변
IDisposable
확산 을 방지하기 위해 단일 방법 내에서 일회용 물체의 사용을 캡슐화해야합니다. Shoelace
다르게 디자인 해보십시오 .
class Shoelace {
bool tied = false;
public void Tie() {
using (var waitHandle = new AutoResetEvent(false)) {
// you can even pass the disposable to other methods
OtherMethod(waitHandle);
// or hold it in a field (but FxCop will complain that your class is not disposable),
// as long as you take control of its lifecycle
_waitHandle = waitHandle;
OtherMethodThatUsesTheWaitHandleFromTheField();
}
}
}
대기 핸들의 범위는 Tie
메소드 로 제한되며 클래스에는 일회용 필드가 없어도되므로 일회용 자체가 아니어도됩니다.
대기 핸들은의 내부 구현 세부 사항 Shoelace
이므로 선언에 새 인터페이스를 추가하는 것과 같이 공용 인터페이스를 변경해서는 안됩니다. 일회용 필드가 더 이상 필요하지 않은 경우 IDisposable
어떻게됩니까? 선언 을 제거 하시겠습니까? Shoelace
추상화 에 대해 생각 하면과 같은 인프라 종속성으로 인해 오염되지 않아야한다는 것을 금방 알 수 있습니다 IDisposable
. IDisposable
결정 론적 정리를 요구하는 자원을 추상화하는 클래스를 위해 예약되어야한다. 즉, 일회용이 추상화의 일부인 클래스의 경우 .
답변
기본적으로 컴포지션 또는 집계를 일회용 클래스와 혼합 할 때 발생합니다. 언급했듯이 첫 번째 방법은 신발 끈에서 waitHandle을 리팩터링하는 것입니다.
그러나 관리되지 않는 리소스가없는 경우 Disposable 패턴을 상당히 제거 할 수 있습니다. (여전히 이것에 대한 공식 참조를 찾고 있습니다.)
그러나 소멸자와 GC를 생략 할 수 있습니다. 그리고 아마도 가상 void Dispose (bool 처분)를 약간 정리합니다.
답변
흥미롭게 Driver
도 위와 같이 정의 된 경우 :
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
때 그런 Shoe
되어 IDisposable
,의 FxCop (v1.36)는 그 불평하지 않습니다 Driver
도해야한다 IDisposable
.
그러나 다음과 같이 정의 된 경우 :
class Driver
{
Shoe leftShoe = new Shoe();
Shoe rightShoe = new Shoe();
}
그러면 불평 할 것입니다.
첫 번째 버전에서는 Shoe
인스턴스가 여전히 생성되고 Driver
여전히 배치되어야하기 때문에 이것이 솔루션이 아니라 FxCop의 제한 사항이라고 생각합니다 .
답변
디자인을 단단히 결합하면 IDisposable이 확산되는 것을 막는 기술적 인 방법이 없다고 생각합니다. 그런 다음 디자인이 옳은지 궁금합니다.
귀하의 예에서, 신발에 신발 끈을 소유하고 운전자가 신발을 소유해야하는 것이 합리적이라고 생각합니다. 그러나 버스는 운전자를 소유해서는 안됩니다. 일반적으로 버스 운전사는 버스를 따라 폐차장으로 이동하지 않습니다.) 운전자와 신발의 경우, 운전자가 자신의 신발을 만드는 일은 거의 없습니다.
다른 디자인은 다음과 같습니다.
class Bus
{
IDriver busDriver = null;
public void SetDriver(IDriver d) { busDriver = d; }
}
class Driver : IDriver
{
IShoePair shoes = null;
public void PutShoesOn(IShoePair p) { shoes = p; }
}
class ShoePairWithDisposableLaces : IShoePair, IDisposable
{
Shoelace lace = new Shoelace();
}
class Shoelace : IDisposable
{
...
}
새로운 디자인은 신발과 운전자의 구체적인 인스턴스를 인스턴스화하고 처리하기 위해 추가 클래스가 필요하기 때문에 불행히도 더 복잡하지만, 이러한 문제는 해결되는 문제에 내재되어 있습니다. 좋은 점은 신발 끈을 처분하기 위해 버스를 더 이상 일회용으로 사용할 필요가 없다는 것입니다.
답변
Inversion of Control을 사용하는 것은 어떻습니까?
class Bus
{
private Driver busDriver;
public Bus(Driver busDriver)
{
this.busDriver = busDriver;
}
}
class Driver
{
private Shoe[] shoes;
public Driver(Shoe[] shoes)
{
this.shoes = shoes;
}
}
class Shoe
{
private Shoelace lace;
public Shoe(Shoelace lace)
{
this.lace = lace;
}
}
class Shoelace
{
bool tied;
private AutoResetEvent waitHandle;
public Shoelace(bool tied, AutoResetEvent waitHandle)
{
this.tied = tied;
this.waitHandle = waitHandle;
}
}
class Program
{
static void Main(string[] args)
{
using (var leftShoeWaitHandle = new AutoResetEvent(false))
using (var rightShoeWaitHandle = new AutoResetEvent(false))
{
var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))}));
}
}
}