Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

struct with reference members : heap or stack?

It is common knowledge that a struct is a value type, and is therefore allocated on the stack (except for specific cases where it is boxed in a class for instance).

However let's consider this struct:

public struct TestStruct
{
    public List<int> items;
}

internal class Program
{
    private static void Main(string[] args)
    {
        TestStruct g;
    }
}

Our TestStruct g is not a member of a class, but a "standalone" variable declared in the Main function. It fits the requirements of a stack-allocated variable.

However, if I write:

g.items = new List<int>();
  1. I suppose items is allocated on the heap isn't it? Does g "go" on the heap as well?
  2. What happens for items when g goes out of scope (i.e. does the GC have to do its job for items)?
  3. What if instead of a List<int> we had a variable of type implementing IDisposable, what would be the best course of action?

PS: I know one could (should?) use a class in this case instead of a struct. I'm just being confused by this specific case.

like image 761
Max Avatar asked May 13 '14 10:05

Max


2 Answers

I suppose items is allocated on the heap isn't it?

Yes. Memory for items will be allocated on the heap.

Does g "go" on the heap as well?

No, struct stays on stack. It just has field which holds reference to items list on heap.

What happens for items when g goes out of scope (i.e. does the GC have to do its job for items)?

If g goes out of scope, then there will be no references to items in application roots. Items will become garbage and will be collected by GC during next garbage collection. Until then items will stay in memory (struct instance will be removed when you'll exit method where you used it).

What if instead of a List we had a variable of type implementing IDisposable, what would be the best course of action?

Best action is implementing IDisposable by your struct. UPDATE: Actually as @MarcGravell pointed - if possible, its better not to use struct in this case.

like image 109
Sergey Berezovskiy Avatar answered Oct 05 '22 20:10

Sergey Berezovskiy


and is therefore allocated on the stack (except for specific cases where it is boxed in a class for instance).

Is wrong. It is allocated as part of the declaring scope. There are probably more scenarios where it is actually on the heap than on the stack (simply: as part of another object - not an object itself), so this is not a good rule.

For your specific questions:

  1. the object that items refers to (the new List<int>()) goes on the heap; the field items is part of the struct, where-ever that is (and just holds the reference - essentially a glorified pointer)
  2. the object will indeed be considered by GC when all references to it are out of scope
  3. that depends on who owns the lifetime of the object; if it is the TestStruct instance, then your best bet then would be for TestStruct to actually be a class that implements IDisposable, and call Dispose() from the class's Dispose()

As additional thoughts: being able to write g.items = new List<int>(); to me indicates that this is a very poor choice of a struct, since mutability and struct do not play nicely together (with lots of unexpected bugs possible). Either:

  • make the struct immutable (i.e. a readonly field, initialized in a custom constructor)
  • make this a class

In either case: a public field is a bad choice - it should really be a property with a get (and possibly a set, if it is a class - but probably not if it remains a struct)

Examples:

public struct TestStruct {
    private readonly List<int> items;
    public List<int> Items { get { return items; } }
    public TestStruct(List<int> items) {
        this.items = items;
    }
}

or:

public sealed class TestClass : IDisposable {
    private SomeDisposable items = new SomeDisposable();
    public SomeDisposable Items { get { return items; } }
    public void Dispose() {
        if(items != null) {
            items.Dispose();
            items = null;
        }
    }
}
like image 45
Marc Gravell Avatar answered Oct 05 '22 20:10

Marc Gravell