IDisposable이 모든 수업에 퍼지는 것을 어떻게 방지합니까?
이 간단한 수업으로 시작하십시오 ...
다음과 같은 간단한 클래스 세트가 있다고 가정 해 봅시다.
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
패턴).
In some cases IDisposable can be ignored because it supports an optional case. For example, WaitHandle implements IDisposable to support a named Mutex. If a name is not being used, the Dispose method does nothing. MemoryStream is another example, it uses no system resources and its Dispose implementation also does nothing. Careful thinking about whether an unmanaged resource is being used or not can be instructional. So can examining the available sources for the .net libraries or using a decompiler.
In terms of correctness, you can't prevent the spread of IDisposable through an object relationship if a parent object creates and essentially owns a child object which must now be disposable. FxCop is correct in this situation and the parent must be IDisposable.
What you can do is avoid adding an IDisposable to a leaf class in your object hierarchy. This is not always an easy task but it's an interesting exercise. From a logical perspective, there is no reason that a ShoeLace needs to be disposable. Instead of adding a WaitHandle here, is it also possible to add an association between a ShoeLace and a WaitHandle at the point it's used. The simplest way is through an Dictionary instance.
If you can move the WaitHandle into a loose association via a map at the point the WaitHandle is actually used then you can break this chain.
To prevent IDisposable
from spreading, you should try to encapsulate the use of a disposable object inside of a single method. Try to design Shoelace
differently:
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();
}
}
}
The scope of the wait handle is limited to the Tie
method, and the class doesn't need to have a disposable field, and so won't need to be disposable itself.
Since the wait handle is an implementation detail inside of the Shoelace
, it shouldn't change in any way its public interface, like adding a new interface in its declaration. What will happen then when you don't need a disposable field anymore, will you remove the IDisposable
declaration? If you think about the Shoelace
abstraction, you quickly realize that it shouldn't be polluted by infrastructure dependencies, like IDisposable
. IDisposable
should be reserved for classes whose abstraction encapsulate a resource that calls for deterministic clean up; i.e., for classes where disposability is part of the abstraction.
This feels a lot like a higher-level design issue, as is often the case when a "quick fix" devolves into a quagmire. For more discussion of ways out, you might find this thread helpful.
Interestingly if Driver
is defined as above:
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
Then when Shoe
is made IDisposable
, FxCop (v1.36) does not complain that Driver
should also be IDisposable
.
However if it is defined like this:
class Driver
{
Shoe leftShoe = new Shoe();
Shoe rightShoe = new Shoe();
}
then it will complain.
I suspect that this is just a limitation of FxCop, rather than a solution, because in the first version the Shoe
instances are still being created by the Driver
and still need to be disposed somehow.
I don't think there is a technical way of preventing IDisposable from spreading if you keep your design so tightly coupled. One should then wonder if the design is right.
In your example, I think it makes sense to have the shoe own the shoelace, and maybe, the driver should own his/her shoes. However, the bus should not own the driver. Typically, bus drivers do not follow buses to the scrapyard :) In the case of drivers and shoes, drivers seldom make their own shoes, meaning they don't really "own" them.
An alternative design could be:
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
{
...
}
The new design is unfortunately more complicated, as it requires extra classes to instantiate and dispose of concrete instances of shoes and drivers, but this complication is inherent to the problem being solved. The good thing is that buses need no longer be disposable simply for the purpose of disposing of shoelaces.
This is basically what happens when you mix Composition or Aggregation with Disposable classes. As mentioned, the first way out would be to refactor the waitHandle out of shoelace.
Having said that, you can strip down the Disposable pattern considerably when you don't have unmanaged resources. (I'm still looking for an official reference for this.)
But you can omit the destructor and GC.SuppressFinalize(this); and maybe cleanup the virtual void Dispose(bool disposing) a bit.
How about using 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))}));
}
}
}
'IT story' 카테고리의 다른 글
Jai 및 Jai-imageio는 어디서 다운로드 할 수 있습니까? (0) | 2020.06.28 |
---|---|
아래에서 위로 스태킹 다이브 (0) | 2020.06.28 |
CSS3 상자 크기 : margin-box; (0) | 2020.06.28 |
Backbone.js에서 하위 뷰를 렌더링하고 추가하는 방법 (0) | 2020.06.28 |
ASP.NET WebApi 대 MVC? (0) | 2020.06.28 |