Używanie klauzuli nie wywołuje Dispose?

Korzystam z programu Visual Studio 2010 do kierowania profilu klienta .NET 4.0. Mam klasę C # do wykrywania, kiedy dany proces się uruchamia / kończy. W tym celu klasa używa klasy ManagementEventWatcher, która jest inicjalizowana jak poniżej;query, scope iwatcher są polami klasy:

query = new WqlEventQuery();
query.EventClassName = "__InstanceOperationEvent";
query.WithinInterval = new TimeSpan(0, 0, 1);
query.Condition = "TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'notepad.exe'";

scope = new ManagementScope(@"\\.\root\CIMV2");

watcher = new ManagementEventWatcher(scope, query);
watcher.EventArrived += WatcherEventArrived;
watcher.Start();

Procedura obsługi zdarzenia EventArrived wygląda tak:

private void WatcherEventArrived(object sender, EventArrivedEventArgs e)
{
    string eventName;

    var mbo = e.NewEvent;
    eventName = mbo.ClassPath.ClassName;
    mbo.Dispose();

    if (eventName.CompareTo("__InstanceCreationEvent") == 0)
    {
        Console.WriteLine("Started");
    }
    else if (eventName.CompareTo("__InstanceDeletionEvent") == 0)
    {
        Console.WriteLine("Terminated");
    }
}

Ten kod jest oparty naartykuł CodeProject. Dodałem połączenie dombo.Dispose() ponieważ wyciekła pamięć: około 32 KB za każdym razem, gdy podnoszony jest EventArrived, raz na sekundę. Wyciek jest oczywisty zarówno dla WinXP, jak i Win7 (64-bit).

Jak na razie dobrze. Starając się być sumiennym dodałemtry-finally klauzula, taka jak ta:

var mbo = e.NewEvent;
try
{
    eventName = mbo.ClassPath.ClassName;
}
finally
{
    mbo.Dispose();
}

Nie ma problemu. Jeszcze lepiej, C #using klauzula jest bardziej zwarta, ale równoważna:

using (var mbo = e.NewEvent)
{
    eventName = mbo.ClassPath.ClassName;
}

Świetnie, tylko teraz przeciek pamięci wrócił. Co się stało?

Cóż, nie wiem. Próbowałem jednak zdemontować dwie wersje za pomocą ILDASM, które są prawie takie same.

IL ztry-finally:

.try
{
  IL_0030:  nop
  IL_0031:  ldloc.s    mbo
  IL_0033:  callvirt   instance class [System.Management]System.Management.ManagementPath [System.Management]System.Management.ManagementBaseObject::get_ClassPath()
  IL_0038:  callvirt   instance string [System.Management]System.Management.ManagementPath::get_ClassName()
  IL_003d:  stloc.3
  IL_003e:  nop
  IL_003f:  leave.s    IL_004f
}  // end .try
finally
{
  IL_0041:  nop
  IL_0042:  ldloc.s    mbo
  IL_0044:  callvirt   instance void [System.Management]System.Management.ManagementBaseObject::Dispose()
  IL_0049:  nop
  IL_004a:  ldnull
  IL_004b:  stloc.s    mbo
  IL_004d:  nop
  IL_004e:  endfinally
}  // end handler
IL_004f:  nop

IL zusing:

.try
{
  IL_002d:  ldloc.2
  IL_002e:  callvirt   instance class [System.Management]System.Management.ManagementPath [System.Management]System.Management.ManagementBaseObject::get_ClassPath()
  IL_0033:  callvirt   instance string [System.Management]System.Management.ManagementPath::get_ClassName()
  IL_0038:  stloc.1
  IL_0039:  leave.s    IL_0045
}  // end .try
finally
{
  IL_003b:  ldloc.2
  IL_003c:  brfalse.s  IL_0044
  IL_003e:  ldloc.2
  IL_003f:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
  IL_0044:  endfinally
}  // end handler
IL_0045:  ldloc.1

Najwyraźniej problemem jest ta linia:

IL_003c:  brfalse.s  IL_0044

co jest równoważneif (mbo != null), więcmbo.Dispose() nigdy nie jest nazywany. Ale jak to możliwe, aby mbo było null, gdyby mógł uzyskać dostęp.ClassPath.ClassName?

Jakieś myśli na ten temat?

Zastanawiam się również, czy to zachowanie pomaga wyjaśnić tutaj nierozwiązaną dyskusję:Przeciek pamięci w usłudze WMI podczas wysyłania zapytań do dzienników zdarzeń.

questionAnswers(3)

yourAnswerToTheQuestion