Value Type vs Reference Type ⚡ Structure Can Make Your Code Super Fast!

Value types and Reference types are two different ways data is handled in memory.

Here’s a simple explanation with examples :

Value Types:

A value type stores the data directly in the memory allocated for the variable. When you assign a value to a variable of a value type, a copy of the value is made.

  • Stored in the stack.

  • The variable holds the actual data.

  • When you assign one value type to another, a copy of the value is made.

Examples of Value Types:

  • Primitive types like int, float, char, bool, struct etc.

  • Structs (user-defined value types).

Example:

int a = 5;  // a holds the value 5
int b = a;  // b holds a copy of the value in a, so b = 5
b = 10;     // b is changed to 10, but a is still 5
  • In this example, a and b are two separate copies. Changing b doesn’t affect a.

Struct (Value Type):

struct ColorStruct {
    public Color color;
}

ColorStruct color1 = new ColorStruct();  
color1.color = Color.red;      

ColorStruct color2 = color1;         
color2.color = Color.green;

Explanation:

  • ColorStruct is a value type. When color1 is assigned to color2, a copy of color1 is made. color1 and color2 are separate instances, each holding their own data.

  • Changing color2.color to Color.green does not affect color1.color, as they are independent copies.

Memory Representation:

color1 --> [ColorStruct Object] { color: Color.red }
color2 --> [ColorStruct Object] { color: Color.green }  (independent copy)
  • Result:

    • color1.color will remain Color.red, while color2.color will be Color.green, as they are separate instances.

Reference Types:

A reference type holds a reference to the actual data stored in the memory heap. When you assign a reference type variable, it copies the reference, not the actual data.

  • Stored in the heap.

  • The variable holds a reference (or pointer) to the actual data in the heap.

  • When you assign one reference type to another, both variables refer to the same data.

Examples of Reference Types:

  • Classes, arrays, strings, delegates, and objects.

Class (Reference Type) :

class ColorClass {
    public Color color;
}

ColorClass color1 = new ColorClass();  
color1.color = Color.red;      

ColorClass color2 = color1;         
color2.color = Color.green;

Explanation:

  • ColorClass is a reference type. When color1 is assigned to color2, both color1 and color2 reference the same object in memory.

  • Changing color2.color to Color.green will also change color1.color, as they both refer to the same instance.

Memory Representation:

color1 --> [ColorClass Object] { color: Color.green }
color2 --> [ColorClass Object] { color: Color.green }  (same object as color1)
  • Result:

    • After color2.color = Color.green, both color1.color and color2.color will be Color.green.

When Should You Use Structs?

  • Use structs for small, simple types that represent single values or a collection of related values. They are ideal for objects that don’t need to be frequently modified.

  • Use structs when you have immutable data, meaning that once the object is created, its data won’t change. Structs perform best when they remain unchanged after creation.

  • Use structs for performance optimization in scenarios where avoiding heap allocations and garbage collection overhead is important, as structs are allocated on the stack.

  • Use structs when you want value semantics, meaning copying the object creates a full copy rather than a reference to the same instance. Good examples are coordinates, colors, or points in 2D/3D space.

BEST EXAMPLE OF STRUCT :

In unity URP when you create a Unlit Shader (.shader), you will find a best example of struct :


CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            // continue...

In above example, structs are used because they are lightweight, value-type data structures suitable for storing small, simple groups of related data in shader programming. Here’s why struct is appropriate in this context:

  1. Efficiency: Shaders need to be extremely efficient, as they are often executed thousands of times per frame (once per vertex or pixel). Using a struct keeps the data tightly packed and avoids the overhead of heap allocation (which happens with classes). Structs are allocated on the stack, providing fast access and cleanup.
  1. Small and Simple: Structs are ideal for representing small, simple data, such as float4 and float2, without needing the features of a class (like inheritance or polymorphism).

By using structs, the shader remains optimized, and the communication between different shader stages is clear and efficient. If you want to know more about basic share click here


Is It Effective to Use Structs?

Structs can be very effective in certain scenarios. Their effectiveness depends on the context:

  1. Small and Simple Data Types: Structs are more efficient than classes because they are allocated on the stack, avoiding heap allocation and reducing garbage collection pressure.

  2. Frequent Copying or Passing: Structs are copied by value, which means that when the size is small, copying is fast. This makes structs effective for frequently copied small objects.


Structs and Memory Management:

  • Stack Allocation: Structs are allocated on the stack (for local variables or when used as fields in another struct). This means that structs are automatically deallocated when they go out of scope, providing faster memory management compared to classes.

  • Size Consideration: Structs are best suited for small data types. A large struct can result in increased memory usage, as it is copied by value, potentially leading to higher memory consumption.

  • Avoid Boxing: Boxing a struct moves it from the stack to the heap, which incurs overhead. To maintain performance, it's important to avoid unnecessary boxing.


Performance:

  • Performance Gain for Small Data: For small objects, structs outperform classes due to their stack allocation, which is faster than heap allocation and doesn’t require garbage collection.

  • Copying Overhead: Structs are copied by value, so if you are frequently copying large structs, the overhead of copying can impact performance. For large objects, classes may be more efficient because they are passed by reference, avoiding expensive copies.

  • Immutability: Structs work best when used with immutable data, where their value semantics (copy by value) ensure that the original data remains unchanged after modifications. This enhances performance by eliminating the need for frequent re-copying.


Garbage Collection and Structs:

  • No Garbage Collection for Stack-Allocated Structs: Since structs are allocated on the stack, they don’t create garbage collection overhead. Once they go out of scope, they are automatically cleaned up.

  • Heap Allocation in Arrays or Fields: If structs are used in arrays or as fields within classes, they may be allocated on the heap, but they still don’t generate as much garbage collection pressure as objects because the GC doesn't track individual struct instances.


When Not to Use Structs:

  • When the size of the data is large: Structs can lead to performance degradation if they are large, as copying a large struct can be costly.

  • When you need polymorphism: Structs don’t support inheritance, so if you need polymorphic behavior (where a class can be substituted by its derived types), classes are more suitable.

  • When mutability is required: Structs are better when immutable. If you need to frequently modify the data, classes are preferable, as changing a struct results in copying the entire data.


Summarized Learning:

In C#, understanding the difference between value types (like structs) and reference types (like classes) is crucial for optimizing performance. Value types are stored on the stack and copied by value, which makes them ideal for small, immutable data. In contrast, reference types are stored on the heap and passed by reference, which allows for efficient handling of larger, complex objects.

Structs are great for reducing memory overhead and garbage collection pressure since they don’t require heap allocation. However, they should be used carefully for smaller, simpler data to avoid unnecessary copying overhead. Classes, with their ability to support polymorphism and reference sharing, are better suited for larger, mutable, and more complex data types.

Where to go:

To learn more, read Microsoft

Thank you for reading! if you like this, lets connect: LinkedIn

1
Subscribe to my newsletter

Read articles from Md Maruf Howlader directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Md Maruf Howlader
Md Maruf Howlader

Passionate Unity Game Developer with blending technical skills and creativity to craft immersive gaming experiences 🎮 . I specialize in gameplay mechanics, visual fidelity, and writing clean code. I’m driven by game design principles and love creating engaging, interactive experiences. Always eager to learn and explore new gaming trends! Let's connect! 🌟🚀