Back to Blog
C#

C# Record Deconstruct: Extract Fields Cleanly

c# record deconstruct: Learn how to use C# record deconstruction to extract fields into variables, including positional records, custom Deconstruct methods, and practi...

C#RecordsDeconstructionSyntax.NET
Illustration of a C# record being split into separate variables, representing deconstruction.

In C#, record types provide a concise way to define immutable data structures. One of their most practical features is deconstruction, which lets you extract individual fields into separate variables using a straightforward syntax. This article explains how c# record deconstruct works, when to use it, and what to watch out for.

What Is Deconstruction for Records?

Deconstruction is the process of breaking a single object into its constituent parts. For a record, that means taking the encapsulated properties and assigning them to individual variables in one statement. The compiler generates a Deconstruct method automatically for records that use positional parameters, but you can also define your own for records with named properties.

The syntax mirrors tuple deconstruction. Given a record Person with FirstName and LastName, you can write:

var person = new Person("Ada", "Lovelace"); var (first, last) = person; Console.WriteLine(first); // Ada Console.WriteLine(last); // Lovelace

This works because the record exposes a Deconstruct method with out parameters matching the property types and order.

Positional Records: Automatic Deconstruction

When you declare a record with positional parameters, the compiler generates a Deconstruct method automatically. The method has one out parameter for each positional parameter, in the same order. For example:

public record Point(int X, int Y);

The generated Deconstruct method looks like this conceptually:

public void Deconstruct(out int X, out int Y) { X = this.X; Y = this.Y; }

You can then deconstruct a Point instance directly:

var point = new Point(3, 4); var (x, y) = point; Console.WriteLine($"x={x}, y={y}");

This automatic generation is one of the key benefits of positional records. It saves you from writing boilerplate code and ensures the deconstruction stays in sync with the constructor parameters.

Custom Deconstruct Methods for Named Records

If your record uses named properties instead of positional parameters, the compiler does not generate a Deconstruct method. You must define one yourself if you want to support deconstruction. For example:

public record Rectangle { public double Width { get; init; } public double Height { get; init; } public void Deconstruct(out double width, out double height) { width = Width; height = Height; } }

Now you can deconstruct a Rectangle instance:

var rect = new Rectangle { Width = 10, Height = 20 }; var (w, h) = rect;

Defining a custom Deconstruct method gives you control over which properties are exposed and in what order. This is useful when you want to provide a different view of the data or only expose a subset of fields.

Using var and Discards in Deconstruction

Deconstruction works with var to infer the types of the new variables. You can also use discards (_) to skip fields you don't need. This is common when you only care about some of the record's data.

var (first, _) = person; // ignore last name

Discards are especially useful when a record has many fields and you need only a few. They also make the intent clear: you are intentionally ignoring that part of the record.

You can also deconstruct into existing variables using the assignment syntax without var:

string a; string b; (a, b) = person;

This can be handy when you want to reuse variables from an earlier scope.

Practical Example: Deconstruction in Pattern Matching

Deconstruction integrates naturally with pattern matching. You can use the positional pattern to match a record and extract its fields in a single step. For instance:

public record Coordinate(int X, int Y); Coordinate point = new(5, 7); if (point is Coordinate(0, 0)) { Console.WriteLine("Origin"); } else if (point is Coordinate(var x, var y) && x > 0) { Console.WriteLine($"Positive x: {x}, y: {y}"); }

Here, the Coordinate(0, 0) pattern checks both fields, while Coordinate(var x, var y) extracts the values into new variables. This approach is concise and keeps the logic close to the data shape.

You can also use deconstruction in a switch expression:

string Describe(Coordinate c) => c switch { (0, 0) => "origin", (var x, 0) => $"on x-axis at {x}", (0, var y) => $"on y-axis at {y}", (var x, var y) => $"at ({x}, {y})" };

The pattern matching syntax leverages the same Deconstruct method, so it works with both positional records and records with custom Deconstruct methods.

Common Pitfalls and Limitations

Deconstruction is straightforward, but there are a few edge cases to keep in mind.

Mismatched variable count – The number of variables on the left side must match the number of out parameters in the Deconstruct method. If you try to deconstruct into fewer or more variables, you get a compile-time error.

Order matters – The order of the variables follows the order of the out parameters. For positional records, that is the order of the positional parameters. For custom methods, you decide the order. This can be a source of confusion if the order is not obvious from the property names.

Reference types and null – If the record instance is null, deconstruction will throw a NullReferenceException when accessing the properties. The generated Deconstruct method does not add null checks. Ensure the object is not null before deconstructing, or handle the null case explicitly.

Records with many fields – Deconstructing a record with a large number of fields into individual variables can reduce readability. In such cases, consider whether you actually need all the fields or if a more targeted approach would be clearer.

Performance and Maintainability Considerations

Deconstruction itself has no runtime cost beyond the property accesses it performs. The generated Deconstruct method is a simple method call, and the JIT compiler can often inline it. There is no boxing or allocation overhead.

From a maintainability perspective, deconstruction can make code more readable by removing repetitive property access. However, overusing it can obscure the source of the data. For example, deconstructing a record into five variables and then passing those variables around separately loses the cohesion of the original object. Use deconstruction when it simplifies the surrounding logic, not just to avoid typing a few dots.

When you define a custom Deconstruct method, keep it in sync with the record's properties. If you add a property later, you may need to update the method to include it or explicitly decide not to expose it. This is a tradeoff: automatic deconstruction for positional records stays in sync by definition, but custom methods give you more control at the cost of manual maintenance.

When to Use Deconstruction vs. Other Approaches

Deconstruction is most valuable when you need to work with several fields of a record in a local context. For example, when passing values to a method or performing calculations, extracting them into named variables can improve clarity.

If you only need one field, direct property access is simpler and more explicit. If you need to pass the entire record to another method, keep the record intact. Deconstruction is not a replacement for object-oriented encapsulation; it is a convenience for local data extraction.

Consider using deconstruction when:

  • You are working with a positional record and need all or most of its fields.
  • You want to use pattern matching with records.
  • You need to ignore some fields using discards.

Avoid deconstruction when:

  • The record has many fields and you only need a couple.
  • The order of fields is not obvious and could lead to errors.
  • You are passing the record itself to another method and want to preserve its type.

In those cases, a more targeted approach will be more readable and less error-prone.

c# record deconstruct: Practical Usage and Code Examples | RYUSLOG DEV