Can you have a class in a struct?

18,638

Solution 1

Yes, you can. The pointer to the class member variable is stored on the stack with the rest of the struct's values, and the class instance's data is stored on the heap.

Structs can also contain class definitions as members (inner classes).

Here's some really useless code that at least compiles and runs to show that it's possible:

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MyStr m = new MyStr();
            m.Foo();

            MyStr.MyStrInner mi = new MyStr.MyStrInner();
            mi.Bar();

            Console.ReadLine();
        }
    }

    public class Myclass
    {
        public int a;
    }

    struct MyStr
    {
        Myclass mc;

        public void Foo()
        {
            mc = new Myclass();
            mc.a = 1;
        }

        public class MyStrInner
        {
            string x = "abc";

            public string Bar()
            {
                return x;
            }
        }
    }
}

Solution 2

The class content gets stored on the heap.

A reference to the class (which is almost the same as a pointer) gets stored with the struct content. Where the struct content is stored depends on whether it's a local variable, method parameter, or member of a class, and whether it's been boxed or captured by a closure.

Solution 3

If one of the fields of a struct is a class type, that field will either hold the identity of a class object or else a null referece. If the class object in question is immutable (e.g. string), storing its identity will effectively also store its contents. If the class object in question is mutable, however, storing the identity will be an effective means of storing the contents if and only if the reference will never fall into the hands of any code which might mutate it once it is stored in the field.

Generally, one should avoid storing mutable class types within a structure unless one of two situations applies:

  1. What one is interested in is, in fact, the identity of the class object rather than its content. For example, one might define a `FormerControlBounds` structure which holds fields of type `Control` and `Rectangle`, and represents the `Bounds` that control had at some moment in time, for the purpose of being able to later restore the control to its earlier position. The purpose of the `Control` field would not be to hold a copy of the control's state, but rather to identify the control whose position should be restored. Generally the struct should avoid accessing any mutable members of the object to which it holds a reference, except in cases where it is clear that such access is referring to the current mutable state of the object in question (e.g. in a `CaptureControlPosition` or `RestoreControlToCapturedPosition` method, or a `ControlHasMoved` property).
  2. The field is `private`, the only methods which read it do so for the purpose of examining its properties without exposing the object itself it to outside code, and the only methods which write it will create a new object, perform all of the mutations that are ever going to happen to it, and then store a reference to that object. One could, for example, design a `struct` which behaved much like an array, but with value semantics, by having the struct hold an array in a private field, and by having every attempt to write the array create a new array with data from the old one, modify the new array, and store the modified array to that field. Note that even though the array itself would be a mutable type, every array instance that would ever be stored in the field would be effectively immutable, since it would never be accessible by any code that might mutate it.

Note that scenario #1 is pretty common with generic types; for example, it's very common to have a dictionary whose "values" are the identities of mutable objects; enumerating that dictionary will return instances of KeyValuePair whose Value field holds that mutable type.

Scenario #2 is less common. There is alas no way to tell the compiler that struct methods other than property setters will modify a struct and their use should thus be forbidden in read-only contexts; one could have a struct that behaved like a List<T>, but with value semantics, and included an Add method, but an attempt to call Add on a read-only struct instance would generate bogus code rather than a compiler error. Further, mutating methods and property setters on such structs will generally perform rather poorly. Such structs can be useful are when they exist as an immutable wrapper on an otherwise-mutable class; if such a struct is never boxed, performance will often be better than a class. If boxed exactly once (e.g. by being cast to an interface type), performance will generally be comparable to a class. If boxed repeatedly, performance can be much worse than a class.

Solution 4

It's probably not a recommended practice to do so: see http://msdn.microsoft.com/en-us/library/ms229017(VS.85).aspx

Reference types are allocated on the heap, and memory management is handled by the garbage collector.

Value types are allocated on the stack or inline and are deallocated when they go out of scope.

In general, value types are cheaper to allocate and deallocate. However, if they are used in scenarios that require a significant amount of boxing and unboxing, they perform poorly as compared to reference types.

Share:
18,638
Mark T
Author by

Mark T

Christian. Physicist. Scientific software design with particular expertise in astrodynamics (satellite orbits). Heavily used languages, most recent first: C#, C++, C, Pascal, Basic, FORTRAN. Dozens of other languages used sporadically, including machine and assembly on up. Experienced in 3D: XNA, DirectX, OpenGL, Performer, more.

Updated on June 12, 2022

Comments

  • Mark T
    Mark T almost 2 years

    Is it possible in C# to have a Struct with a member variable which is a Class type? If so, where does the information get stored, on the Stack, the Heap, or both?

  • Ry-
    Ry- over 10 years
    Could you summarize why in your answer, please? (Links go dead, and all that.)
  • Admin
    Admin over 7 years
    Just curious, why did you cross out stack? Don't structs store all their data on the stack, including pointers to reference members as in this scenario?
  • lozzajp
    lozzajp about 7 years
  • RBT
    RBT about 7 years
    Glad that you mentioned that storage varies with the type of the identifier (local variable, parameter or member). +1.
  • Trident
    Trident over 3 years
    @Ben Voigt, So, the true advantages of Stack allocation of a Struct is only when it is either a local variable and a method parameter? I am guessing Struct does not provide any advantage if it references any heap memory in any form..
  • Ben Voigt
    Ben Voigt over 3 years
    @Trident: I wouldn't say that the advantage is with stack allocation at all. It is with the fact that a struct is "bare" data. No extra allocation required. No associated monitor. No associated vtable. No need to touch it during garbage collection. These are true regardless of whether the larger data structure the struct lies within is the call stack, array, object on heap, or whatever.
  • Trident
    Trident over 3 years
    @Ben Voigt, Thanks for clarifying it, i got all of it but one. "No need to touch it during garbage collection". I am still not sure how it works. Lets say I have a Struct with an array of Int, then it is allocated on managed Heap but with out references. When the local variable goes out of scope, the array on heap is unreachable so the occupied block of int data should be freed by collection process as well right? or the collection means only collecting reference types and data types are never touched by GC whether its Class or Struct?
  • Ben Voigt
    Ben Voigt over 3 years
    @Trident: If you put 100 strings in an array, then during garbage collection, the runtime has to check reachability of 101 items (the array and 100 individual string objects). If you put 100 struct instances in an array, then the garbage collector only has to test reachability on the array itself.
  • Trident
    Trident over 3 years
    @Ben Voigt, so essentially collection means just changing the addresses of the pointer variables? For example, a string array has 5 elements, array’s base address is 0x100, and 5 elements addresses 0x1000, 0x2000, 0x3000, 0x4000, 0x5000. During the collection process the GC assigns these addresses to “null” so that they don’t point to any addresses which makes them deallocated automatically? And for data types there are no addresses so there is no need to do anything specifically?
  • Ben Voigt
    Ben Voigt over 3 years
    @Trident: You have the wrong concept of how garbage collection works. It has to search for any other object pointing to the same "string stored at 0x1000" before it can discard that string instance. The string array can go away when it is unreachable even if some of the objects referenced within will survive. The struct array actually contains it elements, no reference (pointer) involved, so when the array is unreachable, by definition the elements are also unreachable, no analysis is needed to check that at runtime.
  • Trident
    Trident over 3 years
    @BenVoigt, Thanks for the explanation, it is helpful.
  • Sanctus2099
    Sanctus2099 over 2 years
    @user1618054 The fact that the stack is crossed out is correct. Structs have their values stored on stack when they are declared on the stack but they can also be used by classes which are stored on the heap. For instance, a list of structs will hold the memory of the structs on the heap. Further, when you iterate and you have an element of the list on the stack, what you have is a copy of the original data which is stored on the heap.