Introduction to Immutability in C#

by Call me dave... 7. November 2009 07:43

Value Objects from Domain Driven Design, Functional programming and “Many Cores” have made immutability sexy.  However being immutable only means that the code is question is side effect free.  What is more important is how the immutable object will be used.

 

Immutability – I don’t change

In Domain Driven Design Money often given as an example of a Value Object.

public class Money
{
    public decimal Amount { get; set; }
    public string CurrencyCode { get; set; }
}

 

Here the Money class in clearly mutable.  The class includes public methods that can be used by external consumers to modify the Amount and the CurrencyCode. 

var tenDollars = new Money {Amount = 10, CurrencyCode = “AUD”};

tenDollars.Amount = 20; 

 

We can ensure that type is not modifiable by making the the property setters private. 

 

public class Money
{
    public Money(decimal amount, string currencyCode)
    {
        Amount = amount;
        CurrencyCode = currencyCode;
    }

    public decimal Amount { get; private set; }
    public string CurrencyCode { get; private set; }
}

While private setters make the class immutable from external change both Amount and CurrencyCode can both be modified within the class or from a derived class.  Using a read only backing field allows the compiler to enforce the immutability of the type.

public class Money
{
    private readonly decimal _amount;
    private readonly string _currencyCode;

    public Money(decimal amount, string currecyCode)
    {
        _amount = amount;
        _currecyCode = currencyCode;
    }

    public decimal Amount
    {
        get { return _amount; }
    }
    public string CurrencyCode
    {
        get { return _currencyCode; }
    }
}

 

While Value Objects like Money are immutable Domain Entities are not.  Hence the Bank Account or Person’s Wallet Domain Entity will have a public setter on a property of type Money.  Changing the value involves creating a new instance of the Money class.

 

Immutability – I don’t change and am interchangeable

Once an immutable type has been created we need to decide whether instances on the type are interchangeable.  Consider an Audit log entry that is created each time a web page is accessed on a server.  Logically each entry is globally unique since the entry is created for a particular web page request, from a particular IP address at an instant in time.  The Log entry is immutable however  Log Entries are NOT interchangeable. 

Meanwhile Money by its very nature is interchangeable, a ten dollar note has the identical worth to ten one dollar coins.  Further an interchangeable type should be able to be used in the Flyweight Pattern.  Instead of creating a new instance of the Money class each time it is used a cache whose key is based on Amount and CurrencyCode can be used instead.

In C# if a class in Interchangeable it must override the Equals and GetHashCode methods and the ‘==’ an ‘!=’ operators.  Thankfully Resharper will generate this boiler plate code automatically.

public class Money
{
    private readonly decimal _amount;
    private readonly string _currecyCode;

    public Money(decimal amount, string currecyCode)
    {
        _amount = amount;
        _currecyCode = currecyCode;
    }

    public decimal Amount
    {
        get { return _amount; }
    }
    public string CurrencyCode
    {
        get { return _currecyCode; }
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof (Money)) return false;
        return Equals((Money) obj);
    }

    public bool Equals(Money other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return other._amount == _amount && Equals(other._currecyCode, _currecyCode);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (_amount.GetHashCode()*397) ^ (_currecyCode != null ? _currecyCode.GetHashCode() : 0);
        }
    }

    public static bool operator ==(Money left, Money right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Money left, Money right)
    {
        return !Equals(left, right);
    }
}

 

 

Immutability – I don’t change, am interchangeable and emulate mutable operations

A Money type needs to be able to support simple arithmetic operations such as add and subtract.  As the type is immutable instead of modifying the private data of the class a new new instance is created each time the type is modified.  C# enforces the pattern of creating a new instance of the type during the arthimetic operation by making the operators statically defined.

 

public static Money operator + (Money left, Money right)
{
    if(left._currecyCode==right._currecyCode)
        throw new ArgumentException("Only Money of the same currency can be added");
    return new Money(left._amount + right._amount, left.CurrencyCode);
}

public static Money operator -(Money left, Money right)
{
    if (left._currecyCode == right._currecyCode)
        throw new ArgumentException("Only Money of the same currency can be added");
    return new Money(left._amount - right._amount, left.CurrencyCode);
}

public Money ConvertToCurrency(string newCurrencyCode)
{
  return Money(LookupConversionRate(_amount, _currencyCode, newCurrencyCode), newCurrencyCode);

}

In summary

When creating an immutable type you should consider how that type will be used.  Equality functions and operators should be added if it is interchangeable with other instances and mutable operations should be emulated by creating new instances of the type.

Tags: