Class vs Struct

Coming to C#, Struct is actually very similar to Class, it is very difficult to distinguish the difference between Struct and Class and when to use Class and when to use Struct.

Similarities between Struct and Class:

  • Both cover internal information with member data as fields.

  • Both contain methods and properties.

  • Both can implement interfaces

  • Both have constructor functions

  • ...

Differences between Struct and Class:

  • Struct's constructor must have parameters

  • Struct is a reference data type that is contained in the Stack memory area and Class is a reference data type stored in the Heap memory area

  • Struct does not support inheritance from another Class or Struct

  • Struct does not support destructor

  • ...

Because there are many similarities like that, so Struct is considered a "lightweight Class" and can be used to replace Class in some cases. Therefore, you should consider when planning to create a new class of objects. If in case Struct can meet the requirements, you should use Struct rather than Class because objects created by Struct take less space. more memorable than Class due to referencing.

In basic OOP, there is not much distinction between Structure and Class, but when we go deeper into OOP to implement class libraries that can be inherited and extended later, then we will use Class. more than Structure and will see a clear difference because Class is designed for OOP.

Class is Reference Type and Struct is Value Type

Stack vs Heap

Programs need to use RAM when operating. First, let's remember the concept of allocating and deallocating memory areas

  • The program cannot arbitrarily access the memory area but must "ask permission" from the operating system, then the program "owns" this memory area and the operating system ensures that no other program will interfere. This process is called memory allocation(memory allocation)

  • After using the allocated memory, the program needs to "return" it to the operating system (so another program can use it), this process is called memory reclaiming.(memory deallocation)

  • Stack memory : The internal allocation and deallocation process Stackwill operate in LIFO style , meaning that data allocated first will be deallocated later, why is that so?

    • Because the Stack was created to serve the process of calling functions : for example, a function A()calls a function B(), then it A()starts first B()but ends later, then the data in the function A()will be allocated first, then B(); When revoking, it's the opposite, the internal data B()is recalled first, then comesA()

      // Allocate after A()
      // Deallocate before B()
      public void B() { 
          int b = 5;
      }
      // Allocate before B()
      // Deallocate after B()
      public void A() {
          int a = 3;
      }
    • Therefore, it Stackonly contains data generated during the function call (aka local variable), the data in the function will be recovered after the function ends. However, a program must generate data that is outside the scope of the function global variable(not revoked after the function ends).Heap

  • Heap memory : Allocated data will not have a specific order , to access internal data Heapit is necessary through the address (when allocation is complete, the operating system will return this address)

    • The purpose of Heap is to create data global variable, so the above data Heapwill not be automatically recovered after the end of the function (the recovery process needs to be done by the program itself, but the Garbage collector has done it for us).

      public class AllocatedClass { }
      public void A() {
       // Variable "a" will not be automatically revoked after A() ends
       // The recovery process will be handled by Garbage collector
        var c = new AllocatedClass();
      }

Performance and size are notable between Stackand Heap!

  • Stackis much smaller in size Heap, because the Stack only needs to contain local variables, while the Heap needs to contain all "live" data in the program

  • The performance of Stack(allocate/reclaim/retrieve) is Heap much higher , this is obvious because the functions will be called many times in the program

  • In addition, Garbage collector is a component that greatly affects the performance of the program, the more data there is, the Heapmore it will need to be processed.

So the lesson learned is... Take advantage of the Stack, and limit the Heap as much as possible to temporary values

Value type và Reference type

In C# there are 2 data types: Value type (managed data types) and Reference type (unmanaged data types) , each type will represent a number of data types.

  • Reference type : it is the class (recently there is also record ), data types when declared as such Reference typewill always be on the Heap

  • Value type : includes the data types you often see int: float, , bool, byte, char, enum... struct( stringyes class), these data can be located on the Heap or Stack depending on how they are used.

Reference type

As said, classand recordwill always be on Heapno matter whether you declare it is local variablegood or notglobal variable

public class MyClass { }
public record MyRecord { }
void HeapOnly()
{
    // Class/Record instance will always be on the Heap even if it is a local variable
    var onlyHeapClass = new MyClass();
    var onlyHeapRecord = new MyRecord();
}

Value type

Value type ( int, bool, struct, enum) will be on the Stack if it belongs local variableto a function.

public struct StackOnlyStruct { }
void StackOnly() {
    int stackOnlyInt = 5;
    AnotherStackOnly(stackOnlyInt, ref stackOnlyInt, false);
}
void AnotherStackOnly(int valueParameterWillBeCopiedIntoTheFunction,
    ref int useRefModifierForPassingByReferenceToAvoidCopying, //Pass by reference
    in bool useInModifierForPassingByReferenceAndReadonly)  //Pass by reference and readonly
{
    var stackOnlyStruct = new StackOnlyStruct();
}

If structit contains class, it is only classallocated above Heap, but structstill above Stack(and keeps the address of the class instance inside Heap).

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

Value type will be on the Heap if it is in a Reference type

When a Value typebecomes a field of Reference typeit will be on Heap, this of course, because it needs to "live" with this instance (cannot be revoked at the end of the function).

public class MyClass {
    public StackOrHeapStruct AStructFieldWillBeOnHeapToo;
    public int IntAlso;
}
public struct StackOrHeapStruct {
}
void HeapOrStackStruct() {
    // StackOrHeapStruct is a Value type that should be on the Stack
    var onlyStack = new StackOrHeapStruct();
    
    // Now AStructFieldWillBeOnHeapToo will be on the Heap because it is a field of the class
    // Even if it is a Value type
    var myClass = new MyClass {
        AStructFieldWillBeOnHeapToo = new StackOrHeapStruct(),
        IntAlso = 1
    };
}

ref struct will ensure a struct is always on the Stack

Occasionally you will see a declaration type public ref strut MyRefStruct, actually it is nothing special, this is just a declaration that ensures structthis cannot be a field of 1 Reference type(so it cannot be above Heap)

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

The code above will not work compile, because StackOnlyStruct1ref struct

Allocate array on Stack with Span<T>

Before ending, I want to introduce to everyone one ref structcalled Span<T> , usually it is used to point to an array in the Heap (similar to ArraySegment ), we will see another interesting way to use it with the type Sequential data (linear sequence), then you will use []T (array)or List<T>, but both of these are class ( Reference type) and will be on top. Heap If you want to use array on Stack, you need to combine Span<T>and stackalloc(note Tmust be Value type)

public struct Struct {}
public void StackAllocSpan() {
  // It is necessary to declare the size of the span
  // T must be Value type
  Span<int> notPlayWithHeapIntSpan = stackalloc int[3];
  notPlayWithHeapIntSpan[0] = 0;
  Span<Struct> notPlayWithHeapStrutSpan = stackalloc Struct[3];
}

When calling the function, StackAllocSpan();there will be no need to allocate above Heap. However, attention must be paid! Stackhas a small size, when used Span, the internal elements Spanare also located on it Stack(instead of just 1 pointer to data like a normal array), so allocating Span too large can lead to StackOverflow . Span<T>The performance is much better, but it must be noted that it can stackalloc Spanonly be used within the scope of the function, it cannot be returnleaked out, and the methods asyncalso do not allowSpan

Last updated