C# class vs struct vs record (reference types vs value types)

Reference types vs value types

Reference types are allocated on the heap and garbage-collected, whereas value types are allocated on the stack so their (de)allocation are generally cheaper.

Arrays of reference types contain just references to instances of the reference type residing on the heap. Value type arrays hold the actual instance of the value type. Value type arrays are much cheaper than reference type arrays.

Value types get boxed when cast to a reference type or one of the interfaces they implement. They get unboxed when cast back to the value type. Boxes are objects that are allocated on the heap and are garbage-collected. Too much boxing and unboxing can have a negative impact.
In contrast, no such boxing occurs for reference types.

Reference type assignments copy the reference, whereas value type assignments copy the entire value. Assignments of large reference types are cheaper than assignments of large value types.

Reference types are passed by reference, whereas value types are passed by value. Changes to an instance of a reference type affect all references pointing to the instance. Value type instances are copied when they’re passed. When an instance of a value type is changed, it doesn’t affect any of its copies.

You can’t have a null value type. “null” only means a memory address reference is missing.

Class

Class is a reference type. When you compare reference types, this comparison checks if the compared objects are the same object (heap location).

You can specify access modifier if required (private, public, protected, internal).

class declaration

class Person
{
	public string Name {get; set;}
	public int Age {get; set;}
}

create an object

Person person = new Person();
person.Age = 30;
person.Name = "Mario";
Console.WriteLine(person.Name + " " + person.Age); // Mario 30

create another object using the first object we created

Person person2 = person; 
person2.Age = 29;
Console.WriteLine(person.Name + " " + person.Age); // Mario 29
Console.WriteLine(person2.Name + " " + person2.Age); // Mario 29

Though we updated the Age property for person2, the same property for person is also updated. This is because class is a reference type. That means both objects refer to the same place in memory where actual data is stored.

Object references will be saved in a stack memory, and values in a heap memory. For this example two object references represent the same value in the heap memory.

Struct

The struct data is similar to a class, but it’s a value data type. It’s instances and objects are stored on a stack. It’s used to encapsulate a small group of related variables.

You can specify access modifier if required.

struct declaration

struct Person
{
	public string Name {get; set;}
	public int Age {get; set;}
}

create an object

Person person = new Person();
person.Age = 30;
person.Name = "Mario";
Console.WriteLine(person.Name + " " + person.Age); // Mario 30

create another object using the first object we created

Person person2 = person; 
person2.Age = 29;
Console.WriteLine(person.Name + " " + person.Age); // Mario 30
Console.WriteLine(person2.Name + " " + person2.Age); // Mario 29

The change in value did not affect the property for of person. This is because they represent different locations in the memory for each object (they’re saved into stack memory).

when to use struct

  • when the scope of the object is small and short-lived
  • when the data group is small and simple
  • when needing better performance for the system

do not define a sutrct unless the type

  • logically represents a single value, similar to primitive types
  • has an instance size smaller than 16 bytes
  • is immutable
  • will not have to be boxed frequently

Record (c# 9.0 and above)

Records just add a number of default methods to the class or the struct. record class behaves like a class (reference type) and record struct behaves like a struct (value type)

The main difference between a class and a record type, is that a record has the main purpose of storing data, while a class defines responsibility. Records are also immutable, while classes are not.

A record represents a set of data.

  • records are immutable by default
  • they include an overload of ToString()
  • they support value equality

record declaration

public record Person(string Name, int Age);

create an object (you must pass values as constructor params)

Person person = new Person("Mario", 30);
Console.WriteLine(person); // Person { Name = Mario, Age = 30 }

through the usage of with it’s possible to copy an immutable object and change one of the properties

Person person = new Person("Mario", 30);
Person person2 = person with { Age = 25 };
if(person == person2)
	Console.WriteLine(person);

when to use records

Consider using a record in place of a class or struct when:

  • You want to define a data model that depends on value equality
  • You want to define a type for which objects are immutable
  • You have one-way flow DTOs

Reference(s)

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/records
https://www.syncfusion.com/blogs/post/struct-record-class-in-csharp
https://stackoverflow.com/questions/7484735/c-struct-vs-class-faster
https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct?redirectedfrom=MSDN
https://josipmisko.com/posts/c-sharp-class-vs-record
https://stackoverflow.com/questions/75322942/are-record-structs-passed-by-value-or-by-reference-and-can-they-be-blittable-or