C# > Functional Programming > Immutable Types > With Expressions for Records

Creating and Modifying Immutable Records with 'with' Expressions in C#

This example demonstrates how to create immutable records in C# and how to use 'with' expressions to create new records based on existing ones, ensuring immutability.

Introduction to Immutable Records

Records in C# are reference types that provide concise syntax for creating immutable data structures. Immutability ensures that once a record is created, its properties cannot be changed. This promotes predictability and simplifies reasoning about code, especially in concurrent scenarios.

Defining an Immutable Record

This code defines a simple `Person` record with properties for `FirstName`, `LastName`, and `Age`. By default, records are immutable, meaning these properties cannot be directly modified after the record is created.

public record Person(string FirstName, string LastName, int Age);

Creating a Record Instance

This line creates a new instance of the `Person` record with the specified values. Once created, this `person1` instance cannot have its `FirstName`, `LastName`, or `Age` directly changed.

Person person1 = new("John", "Doe", 30);

Using 'with' Expressions for Immutability

The 'with' expression is used to create a new record based on an existing one, but with some properties modified. In this case, `person2` is a new `Person` record created from `person1`, but with the `Age` property set to 31. `person1` remains unchanged, maintaining immutability. This is a non-destructive operation; a new object is created instead of modifying the original.

Person person2 = person1 with { Age = 31 };

Verifying Immutability

This code prints both `person1` and `person2`. You will see that `person1`'s age remains 30, while `person2`'s age is 31, demonstrating that `person1` was not modified. This highlights the immutability achieved through records and 'with' expressions.

Console.WriteLine(person1);
Console.WriteLine(person2);

Complete Code Example

This is the complete code example, demonstrating the creation of a record and the use of the 'with' expression to create a new record with a modified property.

public record Person(string FirstName, string LastName, int Age);

public class Example
{
    public static void Main(string[] args)
    {
        Person person1 = new("John", "Doe", 30);
        Person person2 = person1 with { Age = 31 };

        Console.WriteLine(person1); // Output: Person { FirstName = John, LastName = Doe, Age = 30 }
        Console.WriteLine(person2); // Output: Person { FirstName = John, LastName = Doe, Age = 31 }
    }
}

Concepts Behind the Snippet

The snippet showcases immutability, a core concept in functional programming. Immutability simplifies state management, reduces the risk of side effects, and makes code easier to reason about and test. The 'with' expression provides a convenient way to create new immutable objects based on existing ones, without modifying the original.

Real-Life Use Case

Consider a system processing financial transactions. Each transaction can be represented as an immutable record. When a transaction is modified (e.g., due to a correction), a new transaction record is created using 'with' expressions, preserving the original transaction's history. This is crucial for auditing and maintaining data integrity.

Best Practices

  • Favor immutable data structures whenever possible, especially in multithreaded environments.
  • Use 'with' expressions to create new instances with modifications instead of directly mutating existing objects.
  • Design your records to encapsulate related data logically.

Interview Tip

Be prepared to discuss the benefits of immutability and how it relates to functional programming principles. Explain how 'with' expressions facilitate working with immutable records and the advantages they provide in terms of code maintainability and thread safety.

When to Use Them

Use immutable records when:

  • You need to represent data that should not be modified after creation.
  • You are working in a multithreaded environment where data consistency is critical.
  • You want to apply functional programming principles.

Memory Footprint

Using 'with' expressions to create new records might seem memory-intensive because it creates new objects. However, the CLR can optimize this in many scenarios, especially when most of the fields remain the same. Additionally, the benefits of immutability in terms of simplified state management and reduced risk of bugs often outweigh the potential memory overhead.

Alternatives

Alternatives to records include:

  • Classes with private setters: You can simulate immutability in classes by using private setters and initializing properties in the constructor. However, this is more verbose and requires more manual effort.
  • Structs: While structs are value types, they are not inherently immutable. Making a struct truly immutable requires careful design.

Pros

  • Immutability: Guarantees that data cannot be changed after creation.
  • Concise Syntax: Records provide a cleaner syntax for defining data-centric types.
  • 'with' Expressions: Simplifies creating new instances with modifications.
  • Value-Based Equality: Records automatically implement value-based equality, making comparison easier.

Cons

  • Performance Overhead: Creating new instances for every modification might introduce some performance overhead compared to mutating existing objects.
  • Learning Curve: Developers unfamiliar with functional programming concepts might need time to adapt to immutable data structures.

FAQ

  • Are records value types or reference types?

    Records are reference types. However, they have value-based equality, meaning that two records are considered equal if their properties have the same values.
  • Can I define methods in a record?

    Yes, you can define methods, properties, and events in a record, just like you can in a class.
  • Are records suitable for all scenarios?

    No. If you need to frequently modify data in place and performance is critical, using mutable data structures might be more appropriate. However, for many scenarios, the benefits of immutability outweigh the potential performance cost.