Why are async state machines classes and not structs in Roslyn?
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
In the .NET ecosystem, the Roslyn compiler plays a crucial role in transforming high-level C# code into intermediate language code. One specific area of interest is how the Roslyn compiler handles asynchronous methods. A key aspect of that process is the use of async state machines. This article dives into why these async state machines are implemented as classes, rather than structs, breaking down technical reasons, challenges, and design decisions.
Understanding Async State Machines
Asynchronous programming in C# uses the async and await keywords to manage asynchronous operations more easily. Underneath the hood, when the Roslyn compiler encounters an asynchronous method, it generates what's known as an "async state machine." This state machine tracks the state of the method across asynchronous calls, thus allowing a method to halt and resume its execution.
How Async State Machines Work
An async state machine in C# typically:
- Maintains State: Stores the current state of the method, so it knows where to continue after the
awaitcall. - Manages Control Flow: Implements complex flow-control logic to handle various states of asynchronous execution.
- Contains Local Variables: Saves any local variables and method parameters needed when execution resumes.
Here's a simplified example of what happens when a method is marked as async:
- Continuously updates its variables.
- Transitions across states without incurring the overhead associated with copying the entire structure.
- Garbage Collection: When the state machine is no longer in use, its memory can be efficiently garbage collected.
- Reduced Copying: Classes avoid the inefficiencies and potential errors associated with copying entire data structures, which would be required for structs upon any state change.
- Inheritance Support: Classes support inheritance, which means they can easily implement interfaces like
IAsyncStateMachine. This provides additional flexibility in dynamically replacing or overriding specific parts of the state machine logic. - Containment of Complex Logic: A state machine may involve intricate logic that deals with state transitions, error handling, and completion tasks, which is more naturally encapsulated within a class boundary.
Related reading
- Why are back edges required in the Ford-Fulkerson algorithm?
- Why are Fibonacci numbers significant in computer science?
- Why are hash table expansions usually done by doubling the size?
- Why are Haskell Maps implemented as balanced binary trees instead of traditional hashtables?
- Why are kotlin coroutines called asynchronous?
- Why aren't my scala futures more efficient?
- Why are C 4 optional parameters defined on interface not enforced on implementing class?
- Why are there dashes in a .NET GUID?

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.