Classes, Structures, and Records, Oh My!

ScreenGrab

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?

Most engineers did not answer 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.

An enhanced edition of this article can be found in my book, “Rock Your Code: Coding Standards for Microsoft .NET,” now available on Amazon. This updated version comprises 230 new pages, providing valuable insights to help you and your team produce top-quality code that is easily modifiable.

Secure your copy by visiting the following link: https://bit.ly/CodingStandards8

Classes and structures are 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 to 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 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 against 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 by using object initialization or both as in this example:

Person person = new(email: "dotNetDave@live.com", 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 the results are very close. But there are some differences.

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

CLASS-STRUCT-RECORD-CREATE-CHART

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.

CLASS-STRUCT-RECORD-CREATE-JSON-SERIALIZATION
CLASS-STRUCT-RECORD-CREATE-JSON-DESERIALIZATION

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.

CLASS-STRUCT-RECORD-CREATE-SORT

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 a 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.

Pick up any books by David McCarter by going to Amazon.com: http://bit.ly/RockYourCodeBooks

One-Time
Monthly
Yearly

Make a one-time donation

Make a monthly donation

Make a yearly donation

Choose an amount

$5.00
$15.00
$100.00
$5.00
$15.00
$100.00
$5.00
$15.00
$100.00

Or enter a custom amount

$

Your contribution is appreciated.

Your contribution is appreciated.

Your contribution is appreciated.

DonateDonate monthlyDonate yearly

If you liked this article, please buy David a cup of Coffee by going here: https://www.buymeacoffee.com/dotnetdave

Ā© The information in this article is copywritten and cannot be preproduced in any way without express permission from David McCarter.

3 thoughts on “Classes, Structures, and Records, Oh My!

  1. One word of caution…. Although you can note change a record member to refer to a different instance, there is no guarantee that the instance referred to is itself immutable…… This has led to a number of bad assumptions and bugs [spent a good part of a moth tracking one down in a codebase I was working with….

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.