Visit the bank-account exercise on Exercism to read the full instructions and download the exercise files.
Dig Deeper
Use a lock statement
lock statement
using System;
public class BankAccount
{
private readonly object _lock = new();
private decimal _balance;
private bool _isOpen;
public void Open() => _isOpen = true;
public void Close() => _isOpen = false;
public decimal Balance => _isOpen ? _balance : throw new InvalidOperationException("Account is closed");
public void UpdateBalance(decimal change)
{
if (!_isOpen)
throw new InvalidOperationException("Account is closed");
lock (_lock)
_balance += change;
}
}
First, we define to field to keep track of our account’s balance and whether the account is opened or closed:
private decimal _balance;
private bool _isOpen;
When dealing with monetary amount, _always_ use `decimal` instead of `float` or `double`, as the later suffer from rounding errors.
The Open() and Close() methods change the open state:
public void Open()
{
_isOpen = true;
}
public void Close()
{
_isOpen = false;
}
The Balance property either returns the current balance if the account has been opened, or throws an InvalidOperationException if not:
public decimal Balance
{
get
{
if (!_isOpen)
throw new InvalidOperationException("Account is closed");
return _balance;
}
}
We then get to the core of the exercise: updating the balance in a safe way, without any concurrency issues.
First, we check if the account is not actually closed, in which case we throw an InvalidOperationException:
public void UpdateBalance(decimal change)
{
if (!_isOpen)
throw new InvalidOperationException("Account is closed");
...
}
For that, we can use a lock statement, which takes an object to lock on and ensures that there is never any concurrent execution of the code within the lock’s scope.
Any other process wanting to execute the same code is halted until the currently executing process is done executing the lock’s code block.
As said, you take a lock on an object.
It is good practice to define a separate field for that, of type object:
private readonly object _lock = new object();
We can then lock on this object to safely update our balance:
lock (_lock)
{
_balance += change;
}
As our lock object is unique per instance of the BankAccount class, we won’t have any issues with locking other bank accounts when we lock on that object.
The `lock` statement is syntactic sugar for calls to `Monitor.Enter()` and `Monitor.Exit()`, using a `try/finally` block.
```csharp
lock (_lock)
{
_balance += change;
}
```
gets compiled to:
```csharp
Monitor.Enter(_lock)
try
{
_balance += change;
}
finally
{
Monitor.Exit(_lock);
}
```
Shortening
The lock statement has only one statement in its scope, so we can omit the braces:
lock (_lock)
_balance += change;
The Open() and Close() methods have just one single statement and thus can be written as expression-bodied methods:
public void Open() => _isOpen = true;
public void Close() => _isOpen = false;
The same can be done for the Balance property, using the ternary operator (? :):
public decimal Balance => _isOpen ? _balance : throw new InvalidOperationException();
Finally, we can use a target-typed new expression to replace new object with just new for creating the lock (the compiler can figure out the type from the method’s return type):
private readonly object _lock = new();
Use a Mutex
Mutex
using System;
using System.Threading;
public class BankAccount
{
private readonly Mutex _mutex = new();
private decimal _balance;
private bool _isOpen;
public void Open() => _isOpen = true;
public void Close() => _isOpen = false;
public decimal Balance => _isOpen ? _balance : throw new InvalidOperationException();
public void UpdateBalance(decimal change)
{
if (!_isOpen)
throw new InvalidOperationException("Account is closed");
_mutex.WaitOne();
try
{
_balance += change;
}
finally
{
_mutex.ReleaseMutex();
}
}
}
First, we define to field to keep track of our account’s balance and whether the account is opened or closed:
private decimal _balance;
private bool _isOpen;
When dealing with monetary amount, _always_ use `decimal` instead of `float` or `double`, as the later suffer from rounding errors.
The Open() and Close() methods change the open state:
public void Open()
{
_isOpen = true;
}
public void Close()
{
_isOpen = false;
}
The Balance property either returns the current balance if the account has been opened, or throws an InvalidOperationException if not:
public decimal Balance
{
get
{
if (!_isOpen)
throw new InvalidOperationException("Account is closed");
return _balance;
}
}
We then get to the core of the exercise: updating the balance in a safe way, without any concurrency issues.
First, we check if the account is not actually closed, in which case we throw an InvalidOperationException:
public void UpdateBalance(decimal change)
{
if (!_isOpen)
throw new InvalidOperationException("Account is closed");
...
}
For that, we can use the Mutexclass, which allows programmatically restricting access to a section of code.
This ensures that there is never any concurrent execution of the code within the mutex’s restricted scope.
Any other process wanting to execute the same code is halted until the currently executing process is done executing the mutex’s code block.
Let’s define an instance of the Mutex class:
private readonly Mutex _mutex = new Mutex();
We can then use the Mutex to signal that we want to acquire exclusive access by using its WaitOne() method.
This method will block until there it has been released by any other process that has acquired the mutex.
_mutex.WaitOne();
_balance += change;
_mutex.ReleaseMutex();
We can then safely update the balance and finish off by releasing the mutex.
As our Mutex object is unique per instance of the BankAccount class, we won’t have any issues with locking other bank accounts when we use the mutex.
One potential issue we currently have is that the mutex is not released if the balance updating somehow throw an exception.
To fix this, we’ll release the mutex in a try/finally statement:
try
{
_balance += change;
}
finally
{
_mutex.ReleaseMutex();
}
Shortening
The Open() and Close() methods have just one single statement and thus can be written as expression-bodied methods:
public void Open() => _isOpen = true;
public void Close() => _isOpen = false;
The same can be done for the Balance property, using the ternary operator (? :):
public decimal Balance => _isOpen ? _balance : throw new InvalidOperationException();
Finally, we can use a target-typed new expression to replace new object with just new for creating the `Mutex“ (the compiler can figure out the type from the method’s return type):
private readonly object _mutex = new();
Source: Exercism csharp/bank-account