Question:
Do all CLR value types, including user-defined struct
s, live on the evaluation stack exclusively, meaning that they will never need to be reclaimed by the garbage collector, or are there cases where they are garbage-collected?
Background:
I have previously asked a question on SO about the impact that a fluent interface has on the runtime performance of a .NET application. I was particuarly worried that creating a large number of very short-lived temporary objects would negatively affect runtime performance through more frequent garbage-collection.
Now it has occured to me that if I declared those temporary objects' types as struct
(ie. as user-defined value types) instead of class
, the garbage collector might not be involved at all if it turns out that all value types live exclusively on the evaluation stack.
(This occured to me mainly because I was thinking of C++'s way of handling local variables. Usually being automatic (auto
) variables, they are allocated on the stack and therefore freed when the program execution gets back to the caller — no dynamic memory management via new
/delete
involved at all. I thought the CLR just might handle struct
s similarly.)
What I've found out so far:
I did a brief experiment to see what the differences are in the CIL generated for user-defined value types and reference types. This is my C# code:
struct SomeValueType { public int X; }
class SomeReferenceType { public int X; }
.
.
static void TryValueType(SomeValueType vt) { ... }
static void TryReferenceType(SomeReferenceType rt) { ... }
.
.
var vt = new SomeValueType { X = 1 };
var rt = new SomeReferenceType { X = 2 };
TryValueType(vt);
TryReferenceType(rt);
And this is the CIL generated for the last four lines of code:
.locals init
(
[0] valuetype SomeValueType vt,
[1] class SomeReferenceType rt,
[2] valuetype SomeValueType <>g__initLocal0, //
[3] class SomeReferenceType <>g__initLocal1, // why are these generated?
[4] valuetype SomeValueType CS$0$0000 //
)
L_0000: ldloca.s CS$0$0000
L_0002: initobj SomeValueType // no newobj required, instance already allocated
L_0008: ldloc.s CS$0$0000
L_000a: stloc.2
L_000b: ldloca.s <>g__initLocal0
L_000d: ldc.i4.1
L_000e: stfld int32 SomeValueType::X
L_0013: ldloc.2
L_0014: stloc.0
L_0015: newobj instance void SomeReferenceType::.ctor()
L_001a: stloc.3
L_001b: ldloc.3
L_001c: ldc.i4.2
L_001d: stfld int32 SomeReferenceType::X
L_0022: ldloc.3
L_0023: stloc.1
L_0024: ldloc.0
L_0025: call void Program::TryValueType(valuetype SomeValueType)
L_002a: ldloc.1
L_002b: call void Program::TryReferenceType(class SomeReferenceType)
What I cannot figure out from this code is this:
Where are all those local variables mentioned in the
.locals
block allocated? How are they allocated? How are they freed?(Off-topic: Why are so many anonymous local variables needed and copied to-and-fro, only to initialize my two local variables
rt
andvt
?)