Полагаю, это интересный пример кода.
У нас есть класс - назовем его Test - с методом Finalize . В методе Main есть два кодовых блока, в которых я использую оператор блокировки и вызов Monitor.Enter () . Также у меня есть два экземпляра класса Test .
Эксперимент довольно прост: обнулите переменную Test в блоке блокировки, а затем попробуйте собрать ее вручную с помощью вызова метода GC.Collect .
Итак, чтобы увидеть вызов Finalize , я вызываю метод GC.WaitForPendingFinalizers . Как видите, все очень просто.
По определению оператора lock он открывается компилятором для блока try {...} finally {..}, с Monitor.Enter вызов внутри блока try и Monitor . Затем он выходит из блока finally . Я пытался реализовать блок try-finally вручную.
Я ожидал одинакового поведения в обоих случаях - при использовании блокировки и при использовании Monitor.Enter . Но, удивительно, удивительно, что это другое, как вы можете видеть ниже:
public class Test
{
private string name;
public Test(string name)
{
this.name = name;
}
~Test()
{
Console.WriteLine(string.Format("Finalizing class name {0}.", name));
}
}
class Program
{
static void Main(string[] args)
{
var test1 = new Test("Test1");
var test2 = new Test("Tesst2");
lock (test1)
{
test1 = null;
Console.WriteLine("Manual collect 1.");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Manual collect 2.");
GC.Collect();
}
var lockTaken = false;
System.Threading.Monitor.Enter(test2, ref lockTaken);
try {
test2 = null;
Console.WriteLine("Manual collect 3.");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Manual collect 4.");
GC.Collect();
}
finally {
System.Threading.Monitor.Exit(test2);
}
Console.ReadLine();
}
}
Вывод этого примера:
Ручной сбор 1. Ручной сбор 2.
Ручной сбор 3. Завершение занятия
Название Test2. Ручной сбор 4.
И исключение нулевой ссылки в последнем блоке finally, потому что test2 является нулевой ссылкой.
Я был удивлен и разобрал свой код в IL. Итак, вот дамп IL метода Main :
.entrypoint
.maxstack 2
.locals init (
[0] class ConsoleApplication2.Test test1,
[1] class ConsoleApplication2.Test test2,
[2] bool lockTaken,
[3] bool <>s__LockTaken0,
[4] class ConsoleApplication2.Test CS$2$0000,
[5] bool CS$4$0001)
L_0000: nop
L_0001: ldstr "Test1"
L_0006: newobj instance void ConsoleApplication2.Test::.ctor(string)
L_000b: stloc.0
L_000c: ldstr "Tesst2"
L_0011: newobj instance void ConsoleApplication2.Test::.ctor(string)
L_0016: stloc.1
L_0017: ldc.i4.0
L_0018: stloc.3
L_0019: ldloc.0
L_001a: dup
L_001b: stloc.s CS$2$0000
L_001d: ldloca.s <>s__LockTaken0
L_001f: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
L_0024: nop
L_0025: nop
L_0026: ldnull
L_0027: stloc.0
L_0028: ldstr "Manual collect."
L_002d: call void [mscorlib]System.Console::WriteLine(string)
L_0032: nop
L_0033: call void [mscorlib]System.GC::Collect()
L_0038: nop
L_0039: call void [mscorlib]System.GC::WaitForPendingFinalizers()
L_003e: nop
L_003f: ldstr "Manual collect."
L_0044: call void [mscorlib]System.Console::WriteLine(string)
L_0049: nop
L_004a: call void [mscorlib]System.GC::Collect()
L_004f: nop
L_0050: nop
L_0051: leave.s L_0066
L_0053: ldloc.3
L_0054: ldc.i4.0
L_0055: ceq
L_0057: stloc.s CS$4$0001
L_0059: ldloc.s CS$4$0001
L_005b: brtrue.s L_0065
L_005d: ldloc.s CS$2$0000
L_005f: call void [mscorlib]System.Threading.Monitor::Exit(object)
L_0064: nop
L_0065: endfinally
L_0066: nop
L_0067: ldc.i4.0
L_0068: stloc.2
L_0069: ldloc.1
L_006a: ldloca.s lockTaken
L_006c: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
L_0071: nop
L_0072: nop
L_0073: ldnull
L_0074: stloc.1
L_0075: ldstr "Manual collect."
L_007a: call void [mscorlib]System.Console::WriteLine(string)
L_007f: nop
L_0080: call void [mscorlib]System.GC::Collect()
L_0085: nop
L_0086: call void [mscorlib]System.GC::WaitForPendingFinalizers()
L_008b: nop
L_008c: ldstr "Manual collect."
L_0091: call void [mscorlib]System.Console::WriteLine(string)
L_0096: nop
L_0097: call void [mscorlib]System.GC::Collect()
L_009c: nop
L_009d: nop
L_009e: leave.s L_00aa
L_00a0: nop
L_00a1: ldloc.1
L_00a2: call void [mscorlib]System.Threading.Monitor::Exit(object)
L_00a7: nop
L_00a8: nop
L_00a9: endfinally
L_00aa: nop
L_00ab: call string [mscorlib]System.Console::ReadLine()
L_00b0: pop
L_00b1: ret
.try L_0019 to L_0053 finally handler L_0053 to L_0066
.try L_0072 to L_00a0 finally handler L_00a0 to L_00aa
Я не вижу никакой разницы между оператором lock и вызовом Monitor.Enter .
Итак, почему у меня все еще есть ссылка на экземпляр test1 в случае lock , и объект не собирается GC, но в случае использования Monitor.Enter собрано и доработано?