Tech Off Thread

7 posts

Forum Read Only

This forum has been made read only by the site admins. No new threads or comments can be added.

Question about "using" statement - does it box value types?

Back to Forum: Tech Off
  • User profile image
    BitFlipper

    Let's say I have:

     

    using (new MyValueType(...))
    {
        // ...
    }

     

    Would that work, or would the value type be boxed, and hence it could be GC'd before the using block completes?

     

    Instead, would I need to do this...?

    using (var myType = new MyValueType(...))
    {
        // ...
    }

     

    Simple question but I don't know the answer.

     

  • User profile image
    W3bbo

    The using() block is just syntactic sugar for a try { } finally { } block. So long as the type implements IDisposable then it'll work.

     

    That said, when a structure implements IDisposable things get complicated:

    The compiler will not box the struct if it's only used locally, however if you pass it around as IDisposable then it will be boxed.

     

    btw, how would "using(new MyValueType())" work? You've created an anonymous local, what purpoe does it serve? (unless you're abusing the using() syntax like ASP.NET MVC's HtmlHelpers).

     

    Source: http://haacked.com/archive/2006/08/11/TheUsingStatementAndDisposableValueTypes.aspx

  • User profile image
    BitFlipper

    @W3bbo:

     

    Well, I believe I answered my own question by doing some testing...

    When I put the using statement inside a for loop that iterates 100,000,000 times, GC.GetTotalMemory(false) returns exactly the same amount of memory before and after the test.

    If I now change the struct to a class, the memory used increases by about 500KB. So this tells me that if it is a struct, it isn't boxed. Also, the test completes in about 1/2 the time when it is a struct.

    As to why I want to use this, consider the following:

    public struct LayoutFreezer : IDisposable
    {
        private Control m_control;
        
        public LayoutFreezer(Control control)
        {
            m_control = control;
            m_control.SuspendLayout();
        }
    
        public void Dispose()
        {
            m_control.ResumeLayout();
        }
    }

    Then if I need to make changes while the control's layout is frozen, I just do this:

    using (new LayoutFreezer(this))
    {
        // Make layout changes
        // ...
        // Can exit anywhere without worrying about getting Suspend/Resume out of sync
        //
    }

    I am not using it for this purpose though, and the above is just an example. In my case I am using it in an audio application where changing the audio/MIDI signal graph (adding/removing tracks/busses/fx) could result in lots of recalculations being triggered. I have a similar freeze/thaw pair of methods, and using a similar mechanism as above, I can ensure that the feeze/thaw does not get out of sync.

    In my case though I have other considerations, like wanting to prevent as much garbage as possible from being generated (results in more GC cycles being triggered), which is something you avoid when you are trying to use .Net in a "realtime" application. That is why I want to implement it as a struct and not as a class. My own benchmarks mentioned above proved to me that using a struct in this case is faster as well as having the benefit of not creating any additional garbage.

    EDIT: I am not familiar with how ASP.NET is abusing using() so I don't know whether you would consider my use case as also abusing it or not.

  • User profile image
    wkempf

    I've done something similar without the using but instead with lamdas.

     

    WaitCursor.Do(() => DoSomething());

     

    There's pros and cons to both approaches, and if performance is your key objective your approach is probably better, but I thought I'd still share the alternative.

  • User profile image
    Sven Groot

    @BitFlipper: There's a much easier way to determine if it boxes it or not.

     

    I compiled this code:

    using( new DisposableStruct() )
    {
        Console.WriteLine("Stuff");
    }

     

    And inspected the IL in ildasm:

      .locals init ([0] valuetype csconsoletest.DisposableStruct CS$3$0000)
      IL_0000:  ldloca.s   CS$3$0000
      IL_0002:  initobj    csconsoletest.DisposableStruct
      .try
      {
        IL_0008:  ldstr      "Stuff"
        IL_000d:  call       void [mscorlib]System.Console::WriteLine(string)
        IL_0012:  leave.s    IL_0022
      }  // end .try
      finally
      {
        IL_0014:  ldloca.s   CS$3$0000
        IL_0016:  constrained. csconsoletest.DisposableStruct
        IL_001c:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
        IL_0021:  endfinally
      }  // end handler
    

     

    Which shows pretty plainly that the value type is not boxed anywhere in the process.

     

    And even if it was (or if you did this with a reference type), your concern about the variable being GCed is not valid. The code that the compiler generates for using() must be able to call Dispose in the finally block, therefore it must keep a reference to the object in a local, even if you didn't create a named variable. Therefore the object is guaranteed to be alive (and thus not eligible for collection) until the end of the using block.

     

    You can see that in the IL too; even though I didn't create a variable, the struct's value is stil stored in the local at address 0 on the method's stack.

     

    EDIT: By comparison, explicitly casting a struct to IDisposable (or any interface type, or object of course) does box it:

    DisposableStruct foo = new DisposableStruct();
    ((IDisposable)foo).Dispose();

     

    This becomes:

      .locals init ([0] valuetype csconsoletest.DisposableStruct foo)
      IL_0000:  ldloca.s   foo
      IL_0002:  initobj    csconsoletest.DisposableStruct
      IL_0008:  ldloc.0
      IL_0009:  box        csconsoletest.DisposableStruct
      IL_000e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    

     

    Here you can see that it loads the struct's value directly (ldloc) and then boxes it, whereas above it loaded the struct's address (ldloca) and called Dispose directly. Note that if you just call foo.Dispose() without the explicit cast, it will also not box and look the same as the using example above.

  • User profile image
    davewill

    @Sven Groot: nice show and tell.

  • User profile image
    BitFlipper

    @Sven Groot:

    Yea thanks for the info, especially reminding me that even if it was boxed that a ref would need to be maintained behind the scenes in order to call Dispose on it at the end. This means even for ref types you are safe and don't need to maintain your own ref to it.

    I actually did look at the IL in Reflector previously and did see pretty much exactly what you pointed out. Even when I had a loop with 100,000,000 iterations it still stored each one in the same local variable, and even if that seems obvious it was just good to confirm it for myself.

    I should really get into the habit of using Reflector more with these kinds of questions. I wish though that VS had Debug > Windows > IL in addition to Debug > Windows > Disassembly.

Conversation locked

This conversation has been locked by the site admins. No new comments can be made.