Updating my patient management sample, part two: using the latest C# features
3 min read

Updating my patient management sample, part two: using the latest C# features

In the last post I updated all the projects in my Patient management sample application. It all went smoothly and now I'm going to make use of some of the new language features to improve the code.

Starting with the simple stuff

There's lots of small changes I can make to tidy up the codebase and what's nice is most of these get picked up by Jetbrains Rider which I now use as my default IDE. For example:

Expression-body members

These were added all the way back in c# 6.0 and allow you to clean up the code of single line methods. This means I can go from this:

static string StreamName(Type aggregate, Guid id)
{
    return $"{aggregate.Name}+{id}";
}

to this:

static string StreamName(Type aggregate, Guid id) => 
    $"{aggregate.Name}+{id}";

Nice, simple change but effective. It can even be used with constructors:

public AggregateRepository(IEventStoreConnection connection) =>    
    _connection = connection;

I've gone around and updated all them.

Init only setters

To enforce immutability init only setters have been introduced that only allow you to set the value of an object upon creation. For example:

class EventHandler
{
    public string EventType { get; init; }

    public Action<object> Handler { get; init; }
}

This is a nice feature but I still prefer the use of a constructor as the above still doesn't ensure you set all the required values. Still, I was lazy and used it one place and it's a bit of an improvement.

Target-typed new expressions

Another nice little change which allows you to ignore the type specification if it is already known. Therefore:

static readonly List<RangeLookup> Ranges = new List<RangeLookup> 
{

can become:

static readonly List<RangeLookup> Ranges = new()
{

Program.cs

C# now has the ability to remove the expected Main declaration in Program.cs for console applications. Applications can be written as follows:

using System;

Console.WriteLine("Hello World!");

I tried using this in my sample application but felt it made the code more unreadable if anything. Especially when the code file included additional methods. Maybe I'm just too used to the old way of doing things?

Where I do think it will be useful is when writing scripts.

However, in c# 7.0 the introduced async Main methods that means I can get rid of this ugliness:

public static void Main(string[] args)
{
    AsyncMain().GetAwaiter().GetResult();
}

and replace it with:

public static async Task Main(string[] args)
{
}

Records

Finally, the one I've been waiting for. This is a feature that has existed in F# for some time now and I'm glad its made its way to C#.

This allows you to define an immutable type that is compared by value. Perfect for commands, events and value objects. Before records you'd have to define you commands as follows:

public class AdmitPatient
{
    public AdmitPatient(
        Guid patientId, 
        string patientName, 
        int ageInYears, 
        DateTime timeOfAdmission,
        int wardNumber)
    {
        PatientId = patientId;
        PatientName = patientName;
        AgeInYears = ageInYears;
        TimeOfAdmission = timeOfAdmission;
        WardNumber = wardNumber;
    }

    public Guid PatientId { get; }

    public string PatientName { get; }

    public int AgeInYears { get; }

    public DateTime TimeOfAdmission { get; }

    public int WardNumber { get; }

    protected bool Equals(AdmitPatient other)
    {
        return PatientId.Equals(other.PatientId) && 
            PatientName == other.PatientName &&
            AgeInYears == other.AgeInYears &&      
            TimeOfAdmission.Equals(other.TimeOfAdmission) &&
            WardNumber == other.WardNumber;
    }

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

    public override int GetHashCode()
    {
        return HashCode.Combine(
            PatientId, 
            PatientName, 
            AgeInYears, 
            TimeOfAdmission, 
            WardNumber);
    }
}

Making sure you implement the equality members so the two types can be compared. The other option was to have a base class that used reflection to do the comparison. Equally as horrible.

Now you can remove the all of the junk above by using a record:

public record AdmitPatient(
    Guid PatientId, 
    string PatientName, 
    int AgeInYears, 
    DateTime TimeOfAdmission,
    int WardNumber);

This will generate a type with a constructor accepting all the properties as arguments. With this reduction in code noise it actually makes sense to put them all the commands in the same code file now.

I would also consider looking to use records for value objects as well, although I don't currently have any in my project. Vladimir Khorikov has written a good article about this.

Conclusion

Couple of nice additions to c# that have allowed me to clean up the codebase. I'm particularly happy with records and will spend some more time exploring them later once I've finished upgrading this project.

You can see all the changes I made in this commit.

Next I'm going to upgrade to using the new EventStoreDB gRPC client.