Classes, Structures, And Records, Oh My!

When I interview software engineers, one of the questions I ask (especially for beginners) is:

What is the difference between a class and a structure?

Classes, Structures, and Records, Oh My!

I don’t think most answered with every difference between the two, but one of the reasons I asked the question was to see if they knew how they worked in memory, which would help me understand if the candidate understood how memory management works in .NET.

Classes and structures have been the building blocks for creating applications in .NET. In .NET 5, the new record type was introduced. In this article, we will revisit the most important differences between classes and structures since there have been many changes since version 1.0. I will add records into the discussion too. I will also discuss the major performance differences between the three, and there are differences, some big! First, let’s define these three types.

Classes

Classes in .NET are the core type when creating applications and the major building block for Object-Oriented Programming (OOP). I would say personally, over 99% of the types I create in .NET are classes. Wikipedia defines a class as:

In object-oriented programming, a class is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).In many languages, the class name is used as the name for the class (the template itself), the name for the default constructor of the class (a subroutine that creates objects), and as the type of objects generated by instantiating the class; these distinct concepts are easily conflated. Although, to the point of conflation, one could argue that is a feature inherent in a language because of its polymorphic nature and why these languages are so powerful, dynamic, and adaptable for use compared to languages without polymorphism present. Thus, they can model dynamic systems (i.e. the real world, machine learning, AI) more easily.

Classes are a way to create functions and methods that provide the ability to perform data hiding, the core feature of encapsulation and the first pillar of OOP. When classes are created in memory, we refer to them as objects. When it comes to memory management, all classes are created on the memory heap that is controlled by the Garbage Collector. This is very important since the only way to remove an object from memory is via the Garbage Collector. As developers, we need to be mindful of this, especially for those types that implement IDisposable. If we don’t, we can easily introduce virtual memory leaks into our application. I have a detailed discussion of this in my series of articles titled “Everything That Every .Net Developer Needs to Know About Disposable Types”.

This is how I define one of the classes in my dotNetTips.Spargine.Tester NugGet package,

public sealed class Person: IDataModel < Person, string > , IPerson {
    // Code removed for brevity
}

Member functions (methods) are at the core of classes along with data fields (properties, fields, data members, or attributes). Classes can inherit other classes which allow us to implement inheritance and polymorphism which are two of the other pillars of OOP. Inheritance and polymorphism allow us to implement code reuse. Classes can also implement interfaces. To me, interfaces are a very useful way to create templates for your classes, though that line has gotten blurrier in the last few versions of .NET.

When objects are passed to other parts of the code as a method, the object isn’t passed, only a pointer to that object in memory is passed therefore can be very performant. I bring this up since manipulating an object in a method also manipulates it in the calling code.

Structures

Structures support many of the same features of classes, with some big key differences. Microsoft defines a structure as:

A structure type (or struct type) is a value type that can encapsulate data and related functionality.

Structures in .NET are how we create user-defined types. All value types are structures that include many of the types we use in .NET like DateTime, Integer, Boolean, and many more. All value types are created on the memory stack and are very fast to create and are destroyed at the end of the code block. They are not managed by the Garbage Collector like classes and records, so they do not create virtual memory leaks. When you pass a structure to a method or property, a copy of the entire structure is passed. This could affect performance and memory. Except for reference structures, there exists boxing and unboxing conversions to and from the System.ValueType and System.Object types. Also, this can happen between any interface that it implements. So be careful to avoid boxing as much as you can since it can have a big effect on memory and performance.

This is how I define one of the structures in my dotNetTips.Spargine.Tester NugGet package,

public struct Person: IDataModel < Person, string > , IPerson, IEquatable < Person > {
    //Code removed for brevity
}

To me, the biggest difference between a class and a structure (besides where they live in memory) is that they do not support inheritance so are not part of OOP. Therefore, I typically do not create many of them when I code. They can implement interfaces, just like classes do. Before C# 10, they only supported constructors with parameters.

Another difference is that all fields and properties must be initialized during the creation of a structure since they cannot be null. These fields are typically initialized in the constructor. Classes allow for null, and they support being null themselves.

Records

C# 9 introduced the new record type that makes it easy to create immutable objects which are typically used for data models. The data for the object must be defined during the construction of the object in memory and cannot be changed. We could do this with classes, but records take care of all that “plumbing” code for you. I love records because of that and that is why it’s typically the way I create model classes now. There are some major differences along with performance that I will discuss in the last section.

This is how I define the record I use for testing in my dotNetTips.Spargine.Tester NugGet package,

public sealed record Person: IDataRecord, IComparable < Person > {
    // Code removed for brevity
}

The biggest difference with records is that they are immutable. This is achieved by using the new init modifier on property setters. Auto properties in classes look like this,

public string LastName {get; set; }

In records we use init instead of set like this,

public string LastName {get; init;}

Records can also include mutable properties, but that is not what it was really designed for, and I would caution mixing the two. You might just confuse the developer using your type. The only way to initialize data for a record is in the constructor or by using object initialization or both as in this example,

Person person = new(email: "[email protected]", id: "123456") {
    FirstName = "David",
    HomePhone = "619-555-1234"
    LastName = "McCarter",
};

To make a change to a record, you must create a new object. Here is the syntax on how that is done for changing the person’s home phone number.

var newPerson = person with {HomePhone = "(858) 555-1234"};

The methods that the record type generates for you are:

  • GetHashCode()
  • Equals()
  • ToString()

It also generates the == and != operators. Along with allowing it to be immutable, this saves the creator of the class a lot of time. It also properly implements these methods and operators!

The Differences

Below is a comprehensive list of the differences between classes, structures, and records that I have been able to find or come up with since the Microsoft documentation does not have one. 

Differences
  Class Struct Record
Auto properties Yes Yes Yes
Equality Reference Value Reference
Field initialization Not required Required Not required
Field variable initializers Permitted Not permitted Permitted
Inheritance Yes No Yes
Inherits From Object ValueType Object
Members Constant, field, method, property, event, indexer, operator, constructor, destructor, static constructor, type Constant, field, method, property, event, indexer, operator, constructor, static constructor, type Constant, field, method, property, event, indexer, operator, constructor, destructor, static constructor, type
Memory Location Heap Stack Heap
Modifiers new, public, protected, internal, private, partial, abstract, sealed, static new, public, protected, internal, private, readonly, record, ref new, public, protected, internal, private, partial, abstract, sealed
Mutability Mutable Mutable Immutable
Non-destructive mutation No No Yes: record type only
Parameterless constructor Yes No Yes
Supports Activator.CreateInstance() Yes Yes No
Supports destructor Yes No Yes
Supports partial Yes Yes Yes
Supports read-only No Yes Yes
Type Reference Value Reference
Works with code generators Yes Yes Yes

Performance

Lastly, let’s look at the performance differences between a class, structure, and record. Many of the benchmarking tests that I have done reveal that results are very close. But there are some differences.

First, there is a difference when creating the type in memory.

Classes, Structures, and Records, Oh My!

As you can see, structures are created a lot faster in memory, followed by record then class. Bytes allocated for structure is 0, a record is 88 and class is 128.

Types can be serialized and de-serialized often in applications, especially if it’s a website or web service.

Classes, Structures, and Records, Oh My!

Classes, Structures, and Records, Oh My!

These benchmarks show that classes serialize the fastest and records are the slowest when deserializing. Also, the bytes allocated for the record type are over double compared to classes and structures.

A collection of objects is typically sorted often before they are displayed to the user. In these tests, I am simply using the Sort() method.

Classes, Structures, and Records, Oh My!

In this case, structures are the slowest to sort. I also benchmarked OrderBy(), OrderByDecending(), and more, and the results are similar to Sort().

Summary

I hope that this article helps you to understand the difference between a class, structure, and record types in .NET and will help you decide which one to use depending on what your code needs to do while taking performance into account. More performance data that compares the difference between a class and record can be found in this article Everything You Want to Know About the Record Type in .NET: Performance. There are many more performance tips on the Code & App Performance page on my blog. If you have any comments or suggestions, please make them below.


McCarter Consulting
Software architecture, code & app performance, code quality, Microsoft .NET & mentoring. Available!