Immutable In C#

In C#, an object is considered immutable if it cannot be altered after its creation. Immutability is advantageous in various scenarios, such as when dealing with Data Transfer Objects (DTOs).

Immutable entities maintain a consistent state that cannot be altered, ensuring stability and predictability. This characteristic enhances the ease of debugging and maintaining code by simplifying the understanding of its behavior.

Creating Immutable Objects

In order to create immutable objects in C#, follow best practices such as:

  • Declaring fields to be "readonly" ensures they are allocated just once, usually at object creation.
  • Avoiding mutable properties or methods that modify the object's state after construction.
  • Providing mechanisms to create modified copies of the object with updated values instead of modifying the original object directly.
  • Example:

Let's consider an instance to demonstrate the utilization of Immutable in C#.

Example

Example

using System;
public class Temperature
{
    private readonly double _celsius;
    public Temperature(double celsi_us)
    {
        _celsius = celsi_us;
    }
    public double Celsius => _celsius;
    public double Fahrenheit => _celsius * 9 / 5 + 32;
    public Temperature InCelsius(double celsi_us)
    {
        return new Temperature(celsi_us);
    }
    public Temperature InFahrenheit(double fahrenheit)
    {
        double celsi_us = (fahrenheit - 32) * 5 / 9;
        return new Temperature(celsi_us);
    }
}
public class Demo
{
    public static void Main()
    {
        // Creating an instance of Temperature
        Temperature temp_Celsius = new Temperature(28.0);
        Console.WriteLine($"The temperature in Celsius is: {temp_Celsius.Celsius}");
        Console.WriteLine($"The temperature in Fahrenheit is: {temp_Celsius.Fahrenheit}");
        // A new instance with a different Celsius value is created.
        Temperature newTempCelsius = temp_Celsius.InCelsius(32.0);
        Console.WriteLine($"The new temperature in Celsius is: {newTempCelsius.Celsius}");
        Console.WriteLine($"The new temperature in Fahrenheit is: {newTempCelsius.Fahrenheit}");
        //A new instance with a different Fahrenheit value is created. 
        Temperature newTempFahrenheit = temp_Celsius.InFahrenheit(95.0);
        Console.WriteLine($"The new temperature in Celsius is: {newTempFahrenheit.Celsius}");
        Console.WriteLine($"The new temperature in Fahrenheit is: {newTempFahrenheit.Fahrenheit}");
        //Accessing the original instance to demonstrate immutability.
        Console.WriteLine($"The original temperature in Celsius is: {temp_Celsius.Celsius}");
        Console.WriteLine($"The original temperature in Fahrenheit is: {temp_Celsius.Fahrenheit}");
    }
}

Output:

Output

The temperature in Celsius is: 28
The temperature in Fahrenheit is: 82.4
The new temperature in Celsius is: 32
The new temperature in Fahrenheit is: 89.6
The new temperature in Celsius is: 35
The new temperature in Fahrenheit is: 95
The original temperature in Celsius is: 28
The original temperature in Fahrenheit is: 82.4

Explanation:

In this illustration, the Temperature class is established with Celsius as the fundamental unit of measurement to denote a temperature value. Instances of Temperature are unchangeable; they are initiated with a Celsius value. The class is equipped with the InCelsius and InFahrenheit methods to generate fresh instances with specified temperatures in Celsius or Fahrenheit. Upon instantiation in the Demo class, the temperatures of these instances are displayed in both Celsius and Fahrenheit. Subsequent modifications to the temperature values result in new instances being created, all the while upholding the original values, thereby validating immutability. This practice, when handling temperature-related scenarios, guarantees both thread safety and predictability.

1. Using Value Types

In C#, by default, value types are unchangeable. These encompass structs, enums, and fundamental types such as integer, floating-point numbers, and DateTime. Whenever a new value is assigned to a variable of a value type, a fresh instance is generated.

Example:

Let's consider a scenario to demonstrate the utilization of immutability with value types in the C# programming language.

Example

Example

using System;

public struct ImmutableStruct
{
    public readonly int Value;
    public ImmutableStruct(int num)
    {
        Value = num;
    }
}
class Demo
{
    static void Main(string[] args)
    {
        ImmutableStruct immutable_Object = new ImmutableStruct(256);
        Console.WriteLine("The Initial value is: " + immutable_Object.Value);
        ImmutableStruct new_Immutable_Object = new ImmutableStruct(743);
        Console.WriteLine("The New value is: " + new_Immutable_Object.Value);
    }
}

Output:

Output

The Initial Value is: 256
The New value is: 743

Explanation:

In this instance, ImmutableStruct represents a value type defined as a struct. It includes a sole read-only field named Value, guaranteeing that once a value is assigned in the constructor, it remains unalterable. This property of immutability is maintained due to the fact that structs are value types, and copies of their instances are circulated. Within the Main method, an instance of ImmutableStruct is instantiated with an initial value of 256. The read-only nature of the Value field prohibits any modifications to its value post-instantiation, leading to a compilation error if attempted.

If an alternative value is needed, a fresh ImmutableStruct instance is generated with the specified Value instead of altering the existing one. This showcases the immutability of C# value types.

2. Immutable Objects

These entities are incapable of being altered in their state. Typically, read-only fields are declared, or properties containing only getters and lacking setters are employed to build immutable objects.

Example:

Let's consider a scenario to demonstrate the implementation of immutability using immutable objects in C#.

Example

Example

using System;
public class ImmutableObject
{
    private readonly int _value;
    //Initialise the immutable field using the constructor.
    public ImmutableObject(int num)
    {
        _value = num;
    }
    // Getter property for the immutable field
    public int Value => _value;
}
class Demo
{
    static void Main(string[] args)
    {
        // An ImmutableObject instance is created.
        ImmutableObject obj_ect = new ImmutableObject(437);
        // Accessing the immutable field
        Console.WriteLine("The value of an Immutable Object is: " + obj_ect.Value);
        // Attempting to change the Value (not allowed)
    }
}

Output:

Output

The Value of an Immutable Object is: 437

Explanation:

  • A class called ImmutableObject that has a single immutable field called _Value and represents an immutable object.
  • In order to ensure that its Value cannot be changed after an object is created, the _value field is initialised in the constructor and designated as readonly.
  • The _value field may be accessed read-only via the Value property.
  • An instance of ImmutableObject is created in the Main method, initially assigned 437.
  • The object's immutability is demonstrated by the compilation error that would occur if an attempt was made to change the Value of the immutable field after creation.
  • 3. Functional Programming Constructs

Encouraging immutability is a common practice in functional programming, promoting the avoidance of mutable state. In C#, writing immutable code can be achieved by incorporating functional programming concepts like higher-order functions, lambda expressions, and pure functions.

Example:

Let's consider an instance to demonstrate the utilization of immutability using functional programming concepts in C#.

Example

Example

using System;

using System.Collections.Generic;

using System.Linq;

class Demo
{
   
     static void Main(string[] args)

    {
        // A pure function for two-number addition.
      
      Func<int, int, int> add = (s, p) => s + p;

        // Double a number using a lambda expression.

        Func<int, int> doubleFunc = s => s * 2;

        // Applying a function to every member in a list requires a higher-order function.

        List<int> numbers = new List<int> { 10, 23, 43, 34, 15 };

        List<int> doubledNumbers = Map(numbers, doubleFunc);

        Console.WriteLine("The Doubled Numbers are:");

        foreach (var q in doubledNumbers)

        {

            Console.WriteLine(q);

        }

    }

    // Applying a function to every member in a list requires a higher-order function.

    static List<int> Map(List<int> list, Func<int, int> func)

    {

        List<int> result = new List<int>();

        foreach (int i in list)

        {

            result.Add(func(i));

        }

        return result;

    }

}

Output:

Output

The Doubled Numbers are:
20
46
86
68
30

Explanation:

  • Two integers can be given to our pure function add, which returns their sum.
  • DoubleFunc is a lambda expression that we define to double an integer.
  • A higher-order function called Map is defined. It accepts a list of integers and a function, applies the function to each member in the list, and then returns a new list containing the elements that have been modified.
  • A list of integers is created, and each number is doubled using the doubleFunc lambda expression and the Map function.
  • Finally, it prints the doubled values at the end.
  • Advantages of Immutable:

Several advantages of the Immutable in C# are as follows:

  • Thread Safety: As immutable objects' states cannot change once created, they are thread-safe. It makes concurrent programming simpler and less prone to errors by eliminating the locking method requirement.
  • Predictability: As immutable objects maintain the same state throughout their lifetime, they provide predictable code behaviour. It simplifies reasoning about code, debugging, and maintenance.
  • Concurrency: Immutability promotes safer concurrent programming by removing the possibility of data races and inconsistent states. In multi-threaded applications, immutable data structures improve scalability and speed by enabling shared access without locking.
  • Reusability: As immutable objects cannot be changed, they are naturally shared and reusable. It promotes using functions only on inputs, resulting in predictable outputs and minimising side effects in functional programming.
  • Testing and Debugging: it is often simpler to test and debug because immutable code removes mutable states and side effects. Robust and dependable software is produced as test cases become more reliable and reproducible.
  • Functional Programming Paradigm: Functional programming allows programmers to create code that is more organised, modular, and reusable by combining immutability with concepts like pure functions and referential transparency.
  • Disadvantages of Immutable:

Several disadvantages of the Immutable in C# are as follows:

  • Memory Overhead: As new instances of immutable data structures must be created for each modification, they sometimes demand more memory than their mutable counterparts. It may impact Memory consumption, particularly in situations involving large data sets or frequent updates.
  • Performance Overhead: Immutable data structures can potentially cause performance overhead, especially when adding elements to collections or making frequent modifications, which are common. Computationally expensive operations include copying data and creating new instances.
  • Complexity: Managing immutable data structures can be complicated, particularly when integrating with already present codebases that mostly rely on mutability or when a mutable state is unavoidable. Developers used to mutable paradigms may need to change their views to adjust to an immutable programming method.
  • API Design: In order to ensure consistency and usability, careful thought may be necessary when designing APIs for immutable data structures. A learning curve for developers unfamiliar with immutable concepts may result from the major differences between immutable and mutable APIs.

Input Required

This code uses input(). Please provide values below: