Structs being disposable

Lately I was refactoring a manager class which used internally a private struct to hold instances belonging together in a dictionary. Some referenced instances hold in this struct were disposable, thus implementing the IDisposable interface. The thought of implementing IDisposable on the internal struct came across my mind but then I quickly asked myself what happens with the struct if you implement IDisposable in .NET. I want to present the result of my research in this article.

Because structs are ValueTypes they are often preferred as suitable type for representing lightweight objects such as Points, Rectangles or Colors. Thus structs offer in some cases a less expensive representation of types when dealing with a large number of the same type. Although this advantage might be destroyed if you let the struct implement IDisposable. If you call the dispose method on the struct, the .NET runtime will effectively box your struct into an object type before the dispose method is called. This forces the runtime to assign additional heap space for every struct that needs to be boxed.

This disadvantage can only be overcome if you are declaring your struct in a using statement. The using statement only implicitly calls the dispose method. Calling the dispose method implicitly on the struct will not lead to boxing of the struct type to an object.

As conclusion I recommend to strongly think about implementing IDisposable or event other interfaces on structs because of the fact that boxing and unboxing might occur, thus loosing the performance and cost effectiveness a struct could offer. Only if you can assure that your struct is internally used and you can guarantee that the client of your struct will only use your struct in using statements you might consider implementing IDisposable. Happy coding!

About the author

Daniel Marbach

4 comments

  • I’d say that this “feature” should only be used internally with no outside clients. Outside clients will not be able to always use a “using” statement.

    Happy coding, too.

  • Hi Daniel

    Where did you get the information about boxing of structs? I tried it out and didn’t find any boxing of structs when Dispose is called.

    Try this code and see it by yourself.

    Thomas

    public class ToBoxOrNotToBox
    {
    public struct NormalStruct
    {
    public int first;
    public int last;
    public Image myImage;
    }

    public struct DisposableStruct : IDisposable
    {
    public int first;
    public int last;
    public Image myImage;

    #region IDisposable Members

    public void Dispose()
    {
    if (myImage != null)
    {
    myImage.Dispose();
    }
    }

    #endregion
    }

    public void TryBoxing()
    {
    using (DisposableStruct s = new DisposableStruct() { first = 3, last = 6 })
    {
    }

    DisposableStruct s2 = new DisposableStruct() { first = 33, last = 66, myImage = new Bitmap(30, 30) };
    s2.Dispose();

    NormalStruct s3 = new NormalStruct() {first = 99, last = 999, myImage = new Bitmap(300, 300)};

    int valueType = 23;
    object boxedValueType = valueType;
    int value2 = (int)boxedValueType;
    }

    }

    The ILDasm shows the following IL code:

    .method public hidebysig instance void TryBoxing() cil managed
    {
    // Code size 188 (0xbc)
    .maxstack 4
    .locals init ([0] valuetype DisposableStructs.ToBoxOrNotToBox/DisposableStruct s,
    [1] valuetype DisposableStructs.ToBoxOrNotToBox/DisposableStruct ‘g__initLocal0’,
    [2] valuetype DisposableStructs.ToBoxOrNotToBox/DisposableStruct s2,
    [3] valuetype DisposableStructs.ToBoxOrNotToBox/NormalStruct s3,
    [4] int32 valueType,
    [5] object boxedValueType,
    [6] int32 value2,
    [7] valuetype DisposableStructs.ToBoxOrNotToBox/DisposableStruct ‘g__initLocal1’,
    [8] valuetype DisposableStructs.ToBoxOrNotToBox/NormalStruct ‘g__initLocal2’,
    [9] valuetype DisposableStructs.ToBoxOrNotToBox/DisposableStruct CS$0$0000,
    [10] valuetype DisposableStructs.ToBoxOrNotToBox/NormalStruct CS$0$0001)
    IL_0000: nop
    IL_0001: ldloca.s CS$0$0000
    IL_0003: initobj DisposableStructs.ToBoxOrNotToBox/DisposableStruct
    IL_0009: ldloc.s CS$0$0000
    IL_000b: stloc.1
    IL_000c: ldloca.s ‘g__initLocal0’
    IL_000e: ldc.i4.3
    IL_000f: stfld int32 DisposableStructs.ToBoxOrNotToBox/DisposableStruct::first
    IL_0014: ldloca.s ‘g__initLocal0’
    IL_0016: ldc.i4.6
    IL_0017: stfld int32 DisposableStructs.ToBoxOrNotToBox/DisposableStruct::last
    IL_001c: ldloc.1
    IL_001d: stloc.0
    .try
    {
    IL_001e: nop
    IL_001f: nop
    IL_0020: leave.s IL_0031
    } // end .try
    finally
    {
    IL_0022: ldloca.s s
    IL_0024: constrained. DisposableStructs.ToBoxOrNotToBox/DisposableStruct
    IL_002a: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    IL_002f: nop
    IL_0030: endfinally
    } // end handler
    IL_0031: nop
    IL_0032: ldloca.s CS$0$0000
    IL_0034: initobj DisposableStructs.ToBoxOrNotToBox/DisposableStruct
    IL_003a: ldloc.s CS$0$0000
    IL_003c: stloc.s ‘g__initLocal1’
    IL_003e: ldloca.s ‘g__initLocal1’
    IL_0040: ldc.i4.s 33
    IL_0042: stfld int32 DisposableStructs.ToBoxOrNotToBox/DisposableStruct::first
    IL_0047: ldloca.s ‘g__initLocal1’
    IL_0049: ldc.i4.s 66
    IL_004b: stfld int32 DisposableStructs.ToBoxOrNotToBox/DisposableStruct::last
    IL_0050: ldloca.s ‘g__initLocal1’
    IL_0052: ldc.i4.s 30
    IL_0054: ldc.i4.s 30
    IL_0056: newobj instance void [System.Drawing]System.Drawing.Bitmap::.ctor(int32,
    int32)
    IL_005b: stfld class [System.Drawing]System.Drawing.Image DisposableStructs.ToBoxOrNotToBox/DisposableStruct::myImage
    IL_0060: ldloc.s ‘g__initLocal1’
    IL_0062: stloc.2
    IL_0063: ldloca.s s2
    IL_0065: call instance void DisposableStructs.ToBoxOrNotToBox/DisposableStruct::Dispose()
    IL_006a: nop
    IL_006b: ldloca.s CS$0$0001
    IL_006d: initobj DisposableStructs.ToBoxOrNotToBox/NormalStruct
    IL_0073: ldloc.s CS$0$0001
    IL_0075: stloc.s ‘g__initLocal2’
    IL_0077: ldloca.s ‘g__initLocal2’
    IL_0079: ldc.i4.s 99
    IL_007b: stfld int32 DisposableStructs.ToBoxOrNotToBox/NormalStruct::first
    IL_0080: ldloca.s ‘g__initLocal2’
    IL_0082: ldc.i4 0x3e7
    IL_0087: stfld int32 DisposableStructs.ToBoxOrNotToBox/NormalStruct::last
    IL_008c: ldloca.s ‘g__initLocal2’
    IL_008e: ldc.i4 0x12c
    IL_0093: ldc.i4 0x12c
    IL_0098: newobj instance void [System.Drawing]System.Drawing.Bitmap::.ctor(int32,
    int32)
    IL_009d: stfld class [System.Drawing]System.Drawing.Image DisposableStructs.ToBoxOrNotToBox/NormalStruct::myImage
    IL_00a2: ldloc.s ‘g__initLocal2’
    IL_00a4: stloc.3
    IL_00a5: ldc.i4.s 23
    IL_00a7: stloc.s valueType
    IL_00a9: ldloc.s valueType
    IL_00ab: box [mscorlib]System.Int32
    IL_00b0: stloc.s boxedValueType
    IL_00b2: ldloc.s boxedValueType
    IL_00b4: unbox.any [mscorlib]System.Int32
    IL_00b9: stloc.s value2
    IL_00bb: ret
    } // end of method ToBoxOrNotToBox::TryBoxing

  • Hello Thomas,
    The answer is in the combination of the callvirt statement and the constrained statement. MSDN clearly states that:

    When calling methods of System.Object on value types, consider using the constrained prefix with the callvirt instruction. This removes the need to emit different IL depending on whether or not the value type overrides the method, avoiding a potential versioning problem. Consider using the constrained prefix when invoking interface methods on value types, since the value type method implementing the interface method can be changed using a MethodImpl. These issues are described in more detail in the Constrained opcode.
    http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.callvirt.aspx

    and

    When a callvirt method instruction has been prefixed by constrained thisType, the instruction is executed as follows:

    * If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the ‘this’ pointer to the callvirt of method.
    * If thisType is a value type and thisType implements method then ptr is passed unmodified as the ‘this’ pointer to a call method instruction, for the implementation of method by thisType.
    * If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the ‘this’ pointer to the callvirt method instruction.

    As you can see in your provided code the struct used in the using statement is emitted in a try/catch/finally block. The finally block is constraining your disposable struct “s” before calling callvirt in the following statement:

    IL_0024: constrained. DisposableStructs.ToBoxOrNotToBox/DisposableStruct
    IL_002a: callvirt instance void [mscorlib]System.IDisposable::Dispose()

    but the call to Dispose() on the struct “s2” is not constrained as you can see here:

    IL_0065: call instance void DisposableStructs.ToBoxOrNotToBox/DisposableStruct::Dispose()

    another statement from the documentation proofs:

    Using the constrained prefix also avoids potential versioning problems with value types. If the constrained prefix is not used, different IL must be emitted depending on whether or not a value type overrides a method of System.Object. For example, if a value type V overrides the Object.ToString() method, a call V.ToString() instruction is emitted; if it does not, a box instruction and a callvirt Object.ToString() instruction are emitted.

    Hope that helps
    Daniel

  • Hi Daniel

    Now I see it.

    With the “constrained” IL code the boxing will be taken place ONLY if the struct does NOT implement the method to be called (as Microsoft documents in point 3).

    As long as I implement Dispose() on the struct, there will be no boxing and no performance loss, even when I use the “using” statement.

    Cheers
    Thomas

By Daniel Marbach

Recent Posts