Dispose가 'using'블록에 대해 호출되지 않는 상황이 있습니까?
이것은 전화 인터뷰 질문이었습니다. 사용 블록에 의해 범위가 선언 된 개체에 대해 Dispose가 호출되지 않는 시간이 있습니까?
내 대답은 '아니오'였습니다. 사용 블록 중에 예외가 발생하더라도 Dispose는 계속 호출됩니다.
면접관은 동의하지 않고 - 블록에 using
싸여 있으면 catch 블록에 들어갈 때 Dispose가 호출되지 않는다고 말했습니다 .try
catch
이것은 구조에 대한 나의 이해와 상반되며, 나는 면접관의 관점을 뒷받침하는 어떤 것도 찾을 수 없었습니다. 그가 맞습니까, 아니면 제가 그 질문을 오해했을까요?
void Main()
{
try
{
using(var d = new MyDisposable())
{
throw new Exception("Hello");
}
}
catch
{
"Exception caught.".Dump();
}
}
class MyDisposable : IDisposable
{
public void Dispose()
{
"Disposed".Dump();
}
}
이것은 다음을 생성했습니다.
Disposed
Exception caught
그래서 나는 똑똑한 면접관이 아니라 당신에게 동의합니다 ...
using 블록에서 Dispose가 호출되지 않도록하는 네 가지 사항 :
- 사용 블록 내부에서 컴퓨터의 정전.
- 사용 블록 내부에서 기계가 원자 폭탄에 녹아 있습니다.
StackOverflowException
,AccessViolationException
그리고 아마도 다른 것과 같은 잡을 수없는 예외 .- Environment.FailFast
이상하게도 오늘 아침에 사용 블록에서 Dispose가 호출되지 않는 상황에 대해 읽었습니다. MSDN 에서이 블로그 를 확인하십시오. 전체 컬렉션을 반복하지 않을 때 IEnumerable 및 yield 키워드와 함께 Dispose를 사용하는 것입니다.
불행히도 이것은 예외 사례를 다루지 않습니다. 솔직히 나는 그것에 대해 잘 모르겠습니다. 나는 그것이 끝날 것이라고 예상했지만 아마도 간단한 코드로 확인할 가치가 있습니까?
정전, Environment.FailFast()
반복자 또는 모든 흥미로운 using
것에 의한 부정 행위에 대한 다른 답변 null
입니다. 그러나 나는 그것이 궁금 아무도 내가 생각하는 가장 일반적인 상황 언급되지 찾을 Dispose()
도의 존재에 호출되지 않습니다 using
식 내부가 때 using
예외가 발생합니다.
물론 이것은 논리적입니다.의 표현에 using
예외가 발생했기 때문에 할당이 이루어지지 않았고 우리가 호출 할 수있는 것이 없습니다 Dispose()
. 그러나 삭제 가능한 개체는 이미 절반으로 초기화 된 상태 일 수 있지만 존재할 수 있습니다. 이 상태에서도 이미 관리되지 않는 리소스를 보유 할 수 있습니다. 이것이 일회용 패턴을 올바르게 구현하는 것이 중요한 또 다른 이유입니다.
문제가있는 코드의 예 :
using (var f = new Foo())
{
// something
}
…
class Foo : IDisposable
{
UnmanagedResource m_resource;
public Foo()
{
// obtain m_resource
throw new Exception();
}
public void Dispose()
{
// release m_resource
}
}
Here, it looks like Foo
releases m_resource
correctly and we are using using
correctly too. But the Dispose()
on Foo
is never called, because of the exception. The fix in this case is to use finalizer and release the resource there too.
The using
block gets turned by the compiler into a try
/finally
block of its own, within the existing try
block.
For example:
try
{
using (MemoryStream ms = new MemoryStream())
throw new Exception();
}
catch (Exception)
{
throw;
}
becomes
.try
{
IL_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor()
IL_0005: stloc.0
.try
{
IL_0006: newobj instance void [mscorlib]System.Exception::.ctor()
IL_000b: throw
} // end .try
finally
{
IL_000c: ldloc.0
IL_000d: brfalse.s IL_0015
IL_000f: ldloc.0
IL_0010: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0015: endfinally
} // end handler
} // end .try
catch [mscorlib]System.Exception
{
IL_0016: pop
IL_0017: rethrow
} // end handler
The compiler won't rearrange things. So it happens like this:
- Exception is thrown in, or propagates to, the
using
block'stry
part - Control leaves the
using
block'stry
part, and enters itsfinally
part - Object is disposed by the code in the
finally
block - Control leaves the finally block, and the exception propagates out to the outer
try
- Control leaves the outer
try
and goes into the exception handler
Point being, the inner finally
block always runs before the outer catch
, because the exception doesn't propagate til the finally
block finishes.
The only normal case where this won't happen, is in a generator (excuse me, "iterator"). An iterator gets turned into a semi-complicated state machine, and finally
blocks are not guaranteed to run if it becomes unreachable after a yield return
(but before it has been disposed).
using (var d = new SomeDisposable()) {
Environment.FailFast("no dispose");
}
Yes there is a case when dispose won't be called... you are over thinking it. The case is when the variable in the using block is null
class foo
{
public static IDisposable factory()
{
return null;
}
}
using (var disp = foo.factory())
{
//do some stuff
}
will not throw an exception but would if dispose was called in every case. The specific case that your interviewer mentioned is wrong though.
The interviewer is partially right. Dispose
may not correctly clean up the underlying object on a case-by-case basis.
WCF for example has a few known issues if an exception is thrown while in a using block. Your interviewer was probably thinking of this.
Here is an article from MSDN on how to avoid issues with the using block with WCF. Here is Microsoft's official workaround, although I now think that a combination of that answer and this one is the most elegant approach.
'Programing' 카테고리의 다른 글
이번 주와 달의 첫날을 얻는 방법은 무엇입니까? (0) | 2020.11.13 |
---|---|
NSArray에서 객체의 인덱스를 얻습니까? (0) | 2020.11.13 |
{{object.field}} 존재 여부를 확인하지 않으면 오류 (0) | 2020.11.13 |
Ruby on Rails : 양식으로 배열 제출 (0) | 2020.11.13 |
빈 커밋 메시지로 인해 커밋 중단 (0) | 2020.11.13 |