C# > Functional Programming > Immutable Types > Creating Immutable Classes

Immutable Person Class

This code snippet demonstrates how to create an immutable class in C# representing a person. Immutability ensures that once an instance of the class is created, its state cannot be changed. This is achieved by making all fields read-only and providing a constructor to initialize the object.

Concepts Behind Immutability

Immutability is a key principle in functional programming. An immutable object's state cannot be modified after it is created. This provides several benefits, including thread safety, easier debugging, and increased predictability. To achieve immutability in C#, you typically make all fields private and readonly, and initialize them in the constructor. Properties only provide a getter.

Immutable Person Class Implementation

This snippet defines an immutable `Person` class. - `FirstName`, `LastName`, and `DateOfBirth` are properties with `get; init;`. `init;` only allow assignment during object initialization. - The constructor initializes these fields. - Once the `Person` object is created, its `FirstName`, `LastName`, and `DateOfBirth` cannot be changed.

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
    public DateTime DateOfBirth { get; init; }

    public Person(string firstName, string lastName, DateTime dateOfBirth)
    {
        FirstName = firstName;
        LastName = lastName;
        DateOfBirth = dateOfBirth;
    }

    public override string ToString()
    {
        return $"FirstName: {FirstName}, LastName: {LastName}, DateOfBirth: {DateOfBirth:d}";
    }
}

Usage Example

This shows how to create an instance of the `Person` class. Attempting to modify `FirstName` after the object is created would result in a compiler error, enforcing immutability.

Person person1 = new Person("John", "Doe", new DateTime(1990, 1, 1));
Console.WriteLine(person1);

//The following line would cause a compiler error because FirstName is readonly
//person1.FirstName = "Jane";

Real-Life Use Case

Immutable objects are extremely useful in multithreaded applications. Because their state cannot be modified, they are inherently thread-safe and eliminate the need for locks or other synchronization mechanisms. Also, configuration objects are ideal candidates for immutability. Once configuration settings are loaded, they should not change during the application's lifecycle. Immutable classes are suitable where data integrity and predictability are paramount.

Best Practices

  • Ensure all fields are private and readonly.
  • Initialize all fields in the constructor.
  • If the class contains mutable reference types (e.g., lists), ensure a defensive copy is created when initializing the field in the constructor.
  • Avoid methods that modify the object's state.

Interview Tip

Be prepared to explain the benefits of immutability, such as thread safety and easier debugging. Also, be able to discuss how to handle mutable reference types within an immutable class (e.g., using defensive copying).

When to use them

Use immutable classes when you need to ensure that the state of an object remains constant throughout its lifetime. This is particularly important in concurrent programming scenarios or when dealing with data that should not be modified after creation.

Memory Footprint

The memory footprint of an immutable class is generally the same as a mutable class with the same data. However, when you need to 'modify' an immutable object, you create a new instance with the updated values. This can lead to more memory allocation and garbage collection overhead compared to directly modifying a mutable object. Consider if creating new objects impacts performance negatively for a particular scenario.

Alternatives

While immutability is a valuable concept, alternatives exist:

  • Mutable Classes with Controlled Access: Limit the ability to change the state of a mutable class through access modifiers (e.g., making properties read-only after initialization) and careful method design.
  • Data Transfer Objects (DTOs): For simple data containers, DTOs can be mutable, especially if used only for transferring data between layers.

Pros

  • Thread Safety: Eliminates the need for synchronization primitives.
  • Predictability: Makes debugging and reasoning about code easier.
  • Simplified Testing: State is constant, simplifying unit testing.

Cons

  • Performance Overhead: Creating new instances for every 'modification' can be expensive.
  • Increased Memory Consumption: Due to the creation of new objects.
  • Code Complexity: Can require more code to perform certain operations compared to mutable objects.

FAQ

  • What happens if I need to update a property in an immutable class?

    You need to create a new instance of the class with the updated property value. The original object remains unchanged.
  • Are immutable classes always the best choice?

    No. While they offer several benefits, they can introduce performance overhead. Consider the trade-offs and choose based on your specific needs.