Я полагаю, что это интересный пример кода.
У нас есть класс - назовем его Test - с помощью метода Finalize. В методе Основной есть два блока кода, в которых я использую оператор блокировки и вызов Monitor.Enter(). Кроме того, у меня есть два экземпляра класса Test. Эксперимент довольно прост: залейте переменную Test в блокирующий блок, а затем попытайтесь собрать ее вручную с помощью вызова метода GC.Collect. Итак, чтобы увидеть вызов Finalize, я вызываю метод GC.WaitForPendingFinalizers. Все очень просто, как вы можете видеть.
По определению оператора lock он был открыт компилятором для try {...} наконец {..} блок с вызовом Monitor.Enter внутри блока try и Монитор. Затем он выходит из блока 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 Основной:
.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
Я не вижу никакой разницы между оператором блокировка и вызовом Monitor.Enter. Итак, почему у меня все еще есть ссылка на экземпляр test1 в случае блокировки, и объект не собирается GC, но в случае использования Monitor.Enter собирается и завершается?