I'm using Visual Studio 2010 to target .NET 4.0 Client Profile. I have a C# class to detect when a given process starts/terminates. For this the class uses a ManagementEventWatcher, which is initialised as below; query, scope and watcher are class fields:
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();
The handler for event EventArrived looks like this:
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");
    }
}
This code is based on a CodeProject article. I added the call to mbo.Dispose() because it leaked memory: about 32 KB every time EventArrived is raised, once per second. The leak is obvious on both WinXP and Win7 (64-bit).
So far so good. Trying to be conscientious I added a try-finally clause, like this:
var mbo = e.NewEvent;
try
{
    eventName = mbo.ClassPath.ClassName;
}
finally
{
    mbo.Dispose();
}
No problem there. Better still, the C# using clause is more compact but equivalent:
using (var mbo = e.NewEvent)
{
    eventName = mbo.ClassPath.ClassName;
}
Great, only now the memory leak is back. What happened?
Well, I don't know. But I tried disassembling the two versions with ILDASM, which are almost but not quite the same.
IL from try-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 from using:
.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
Apparently the problem is this line:
IL_003c:  brfalse.s  IL_0044
which is equivalent to if (mbo != null), so mbo.Dispose() is never called. But how is it possible for mbo to be null if it was able to access .ClassPath.ClassName?
Any thoughts on this?
Also, I'm wondering if this behaviour helps explain the unresolved discussion here: Memory leak in WMI when querying event logs.
 
     
     
     
    