C #을 사용하면 다음을 수행 할 수 있습니다 (MSDN의 예).
using (Font font3 = new Font("Arial", 10.0f),
font4 = new Font("Arial", 10.0f))
{
// Use font3 and font4.
}
하면 어떻게됩니까 font4 = new Font
던져? 내가 이해하는 것에서 font3는 리소스를 누출하고 폐기되지 않을 것입니다.
- 이것이 사실입니까? (font4는 폐기되지 않습니다)
- 이것은
using(... , ...)
중첩 사용을 위해 아예 피해야 함을 의미합니까 ?
답변
아니.
컴파일러는 finally
각 변수에 대해 별도의 블록을 생성 합니다.
사양 (§8.13)는 말한다 :
자원 획득이 지역 변수 선언의 형태를 취하면 주어진 유형의 여러 자원을 획득 할 수 있습니다.
using
양식 의 진술using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement
중첩 using 문 시퀀스와 정확히 동일합니다.
using (ResourceType r1 = e1) using (ResourceType r2 = e2) ... using (ResourceType rN = eN) statement
답변
업데이트 : 나는 여기 에서 찾을 수있는 기사의 기초로이 질문을 사용했다 ; 이 문제에 대한 추가 논의는 그것을 참조하십시오. 좋은 질문에 감사드립니다!
Schabse의 답변 은 물론 정확하고 질문에 대한 답변 이지만 질문하지 않은 중요한 변형이 있습니다.
하면 어떻게됩니까
font4 = new Font()
발생 후 관리되지 않는 리소스가 생성자가 아니라 할당 된 전 에서의 ctor 반환 및 채우기font4
기준으로?
좀 더 명확히하겠습니다. 다음이 있다고 가정합니다.
public sealed class Foo : IDisposable
{
private int handle = 0;
private bool disposed = false;
public Foo()
{
Blah1();
int x = AllocateResource();
Blah2();
this.handle = x;
Blah3();
}
~Foo()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (this.handle != 0)
DeallocateResource(this.handle);
this.handle = 0;
this.disposed = true;
}
}
}
이제 우리는
using(Foo foo = new Foo())
Whatever(foo);
이것은
{
Foo foo = new Foo();
try
{
Whatever(foo);
}
finally
{
IDisposable d = foo as IDisposable;
if (d != null)
d.Dispose();
}
}
확인. Whatever
던진다 고 가정하자 . 그런 다음 finally
블록이 실행되고 리소스가 할당 해제됩니다. 문제 없어요.
Blah1()
던진다 고 가정하자 . 그런 다음 리소스가 할당되기 전에 throw가 발생합니다. 객체가 할당되었지만 ctor는 반환 foo
되지 않으므로 채워 try
지지 않습니다 finally
. 개체 참조가 분리되었습니다. 결국 GC는이를 발견하여 종료 자 대기열에 넣습니다. handle
여전히 0이므로 종료자는 아무 작업도 수행하지 않습니다. 종료자는 생성자가 완료되지 않은 객체가 종료되는 상황에서 견고해야합니다 . 당신은있다 필요한 이 강한 파이널 라이저를 작성. 이것이 최종 결정자를 전문가에게 맡기고 직접 시도하지 않아야하는 또 다른 이유입니다.
Blah3()
던진다 고 가정하자 . 리소스가 할당 된 후에 throw가 발생합니다. 그러나 다시, foo
채워지지 않고, 우리는 절대로 입력하지 않으며 finally
, 객체는 종료 자 스레드에 의해 정리됩니다. 이번에는 핸들이 0이 아니므로 종료자가이를 정리합니다. 다시 말하지만, 종료자는 생성자가 성공한 적이없는 객체에서 실행되지만 종료자는 어쨌든 실행됩니다. 이번에는해야 할 일이 있었기 때문에 분명히해야합니다.
이제 던졌다 고 가정 Blah2()
합니다. 던지는 리소스가 할당 된 후, 채워 지기 전에 발생합니다 handle
! 다시 말하지만, 파이널 라이저는 실행되지만 지금 handle
은 여전히 0이고 핸들을 누출합니다!
이 누출이 발생하지 않도록하려면 매우 영리한 코드 를 작성해야합니다 . 자, 당신의 Font
자원 의 경우 도대체 누가 신경 쓰나요? 글꼴 핸들이 누출됩니다. 그러나 예외의시기에 관계없이 관리되지 않는 모든 리소스를 정리 하도록 절대적으로 요구 하는 경우 매우 어려운 문제가 발생합니다.
CLR은 잠금으로이 문제를 해결해야합니다. C # 4 이후 lock
문 을 사용하는 잠금은 다음과 같이 구현되었습니다.
bool lockEntered = false;
object lockObject = whatever;
try
{
Monitor.Enter(lockObject, ref lockEntered);
lock body here
}
finally
{
if (lockEntered) Monitor.Exit(lockObject);
}
Enter
매우 신중하게 그렇게 기록 된 예외가 던져 상관없이가 , lockEntered
true로 설정 하고있는 경우에만 경우 잠금이 실제로 촬영했다. 비슷한 요구 사항이 있다면 실제로 작성해야 할 것은 다음과 같습니다.
public Foo()
{
Blah1();
AllocateResource(ref handle);
Blah2();
Blah3();
}
그리고 내부 에서 무슨 일이 일어나더라도 할당을 해제해야 할 때만 채워지 도록 AllocateResource
똑똑하게 작성하십시오 .Monitor.Enter
AllocateResource
handle
그렇게하는 기술을 설명하는 것은이 답변의 범위를 벗어납니다. 이 요구 사항이 있으면 전문가에게 문의하십시오.
답변
@SLaks 답변에 대한 보완으로 다음은 코드에 대한 IL입니다.
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 74 (0x4a)
.maxstack 2
.entrypoint
.locals init (
[0] class [System.Drawing]System.Drawing.Font font3,
[1] class [System.Drawing]System.Drawing.Font font4,
[2] bool CS$4$0000
)
IL_0000: nop
IL_0001: ldstr "Arial"
IL_0006: ldc.r4 10
IL_000b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
IL_0010: stloc.0
.try
{
IL_0011: ldstr "Arial"
IL_0016: ldc.r4 10
IL_001b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
IL_0020: stloc.1
.try
{
IL_0021: nop
IL_0022: nop
IL_0023: leave.s IL_0035
} // end .try
finally
{
IL_0025: ldloc.1
IL_0026: ldnull
IL_0027: ceq
IL_0029: stloc.2
IL_002a: ldloc.2
IL_002b: brtrue.s IL_0034
IL_002d: ldloc.1
IL_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0033: nop
IL_0034: endfinally
} // end handler
IL_0035: nop
IL_0036: leave.s IL_0048
} // end .try
finally
{
IL_0038: ldloc.0
IL_0039: ldnull
IL_003a: ceq
IL_003c: stloc.2
IL_003d: ldloc.2
IL_003e: brtrue.s IL_0047
IL_0040: ldloc.0
IL_0041: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0046: nop
IL_0047: endfinally
} // end handler
IL_0048: nop
IL_0049: ret
} // end of method Program::Main
중첩 된 try / finally 블록에 유의하십시오.
답변
이 코드 (원래 샘플 기반) :
using System.Drawing;
public class Class1
{
public Class1()
{
using (Font font3 = new Font("Arial", 10.0f),
font4 = new Font("Arial", 10.0f))
{
// Use font3 and font4.
}
}
}
다음 CIL을 생성합니다 ( Visual Studio 2013 에서 .NET 4.5.1을 대상으로 함 ).
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 82 (0x52)
.maxstack 2
.locals init ([0] class [System.Drawing]System.Drawing.Font font3,
[1] class [System.Drawing]System.Drawing.Font font4,
[2] bool CS$4$0000)
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: ldstr "Arial"
IL_000d: ldc.r4 10.
IL_0012: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string,
float32)
IL_0017: stloc.0
.try
{
IL_0018: ldstr "Arial"
IL_001d: ldc.r4 10.
IL_0022: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string,
float32)
IL_0027: stloc.1
.try
{
IL_0028: nop
IL_0029: nop
IL_002a: leave.s IL_003c
} // end .try
finally
{
IL_002c: ldloc.1
IL_002d: ldnull
IL_002e: ceq
IL_0030: stloc.2
IL_0031: ldloc.2
IL_0032: brtrue.s IL_003b
IL_0034: ldloc.1
IL_0035: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_003a: nop
IL_003b: endfinally
} // end handler
IL_003c: nop
IL_003d: leave.s IL_004f
} // end .try
finally
{
IL_003f: ldloc.0
IL_0040: ldnull
IL_0041: ceq
IL_0043: stloc.2
IL_0044: ldloc.2
IL_0045: brtrue.s IL_004e
IL_0047: ldloc.0
IL_0048: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_004d: nop
IL_004e: endfinally
} // end handler
IL_004f: nop
IL_0050: nop
IL_0051: ret
} // end of method Class1::.ctor
보시다시피 try {}
블록은에서 발생하는 첫 번째 할당 이후까지 시작되지 않습니다 IL_0012
. 언뜻보기에 이것은 보호되지 않은 코드에서 첫 번째 항목을 할당하는 것처럼 보입니다 . 그러나 결과는 위치 0에 저장됩니다. 두 번째 할당이 실패하면 외부 finally {}
블록이 실행되고 이는 위치 0, 즉의 첫 번째 할당에서 객체를 가져와 font3
해당 Dispose()
메서드를 호출합니다 .
흥미롭게도 dotPeek 를 사용 하여이 어셈블리를 디 컴파일 하면 다음과 같은 재구성 된 소스가 생성됩니다.
using System.Drawing;
public class Class1
{
public Class1()
{
using (new Font("Arial", 10f))
{
using (new Font("Arial", 10f))
;
}
}
}
디 컴파일 된 코드는 모든 것이 정확하고 using
기본적으로 중첩 된 using
s 로 확장 되는지 확인합니다 . CIL 코드는보기에 약간 혼란스럽고, 무슨 일이 일어나고 있는지 제대로 이해하기 전에 몇 분 동안 그것을 쳐다보아야했습니다. 이. 그러나 생성 된 코드는 공격 할 수없는 진실입니다.
답변
다음은 @SLaks 답변을 증명하는 샘플 코드입니다.
void Main()
{
try
{
using (TestUsing t1 = new TestUsing("t1"), t2 = new TestUsing("t2"))
{
}
}
catch(Exception ex)
{
Console.WriteLine("catch");
}
finally
{
Console.WriteLine("done");
}
/* outputs
Construct: t1
Construct: t2
Dispose: t1
catch
done
*/
}
public class TestUsing : IDisposable
{
public string Name {get; set;}
public TestUsing(string name)
{
Name = name;
Console.WriteLine("Construct: " + Name);
if (Name == "t2") throw new Exception();
}
public void Dispose()
{
Console.WriteLine("Dispose: " + Name);
}
}