Loading...
Skip to Content

C# 9.0

Blurring the lines between object-oriented programming and functional programming

C# has always been a very object-oriented language. Initially based on Java, over the years it evolved and improved, but always been close to it's Java roots. Recently there has been a shift in focus to introduce more features you would see in functional programming languages.

"C# started out very object-oriented, and has been adopting functional features over time. Starting over, I would probably try to strike a balance between the two from the get-go."
Mads Torgersen - Microsoft Lead C# Language Designer

Immutability

A lot of the features you will start to see in C# 9.0 that I would consider 'functional' are grounded in the concept of immutability. Normally C# objects are buckets or data that can be sent to various methods and the object manipulated and changed, in functional programming, this is a no-no as object are immutable as default (in F# the mutable keyword is required)

let mutable x = 10  
x <- 15

Init-Only Properties

This first hint at immutability in C# 9.0 are the init-only properties.

Object initializers were introduced in C# 3.0 and have been around for quite some time now. They provide great flexibility and ease of use in a readable format and can be easily nested.

public class Manufacturer
{
    public string Name { get; set; }
}

public class Vehicle
{
    public string Name { get; set; }
    public Manufacturer Manufacturer { get; set; }
}

Without object initializers or constructors, the code would look something like this.

Manufacturer ford = new Manufacturer()
ford.Name = "Ford";

Vehicle car = new Vehicle();
car.Name = "Mondeo";
car.Manufacturer = ford;

but with object initializers the code is a more readable and concise.

var car = new Vehicle { Name = "Mondeo", Manufacturer = new Manufacturer { Name = "Ford" }};

Init-only properties expand on object initializers by making properies immutable.

public class Manufacturer
{
    public string Name { get; init; }
}

public class Vehicle
{
    public string Name { get; init; }
    public Manufacturer Manufacturer { get; init; }
}

Notice init has now replaced set, init is derived from set, but will only allow the property to be set with object initializers or constructors.

Now if we try the code we did above

Manufacturer ford = new Manufacturer()
ford.Name = "Ford"; //ERROR

Vehicle car = new Vehicle();
car.Name = "Mondeo"; //ERROR
car.Manufacturer = ford; //ERROR

var car = new Vehicle { 
  Name = "Mondeo", Manufacturer = new Manufacturer { Name = "Ford" }
}; //OKAY
car.Name = "Fiesta"; //ERROR

Init accessors

In addition to Init-only properties, there are also init accessors which can be used with fields (and more importantly readonly fields, again making them immutable).

public class Vehicle
{
    private readonly string name = string.Empty;

    public string Name 
    { 
        get => name; 
        init => name = (value ?? throw new ArgumentNullException(nameof(Name)));
    }
}

Records

Records have to be the biggest paradigm shift to functional programming in C#. By default properties of classes have been mutable and can change at various points in the code, this is very object-oriented and what C# has been very good for. But there are times where we don't want this to happen and this isn't easy to achieve in C#. Init-only properties and init accessors are a step towards this making parts of a mutable class immutable, records take this a step further, they are immutable classes.

public record Vehicle
{
    public string? Name { get; init; }
    public int? FirstProduced { get; init; }
}


With expressions

So the next question is "What happens when I want to modify an object", when working with immutable objects you need to think of each object as a snapshot, unlike mutable objects which change along the journey.

public record Manufacturer
{
    public string Name { get; init; }
}

public record Vehicle
{
    public string Name { get; init; }
    public Manufacturer Manufacturer { get; init; }
}

So now we can create a car 'Ford Mondeo' and then create a 'Ford Fiesta' using the with expression

var mondeo = new Vehicle { 
  Name = "Mondeo", Manufacturer = new Manufacturer { Name = "Ford" }
};

var fiesta = mondeo with { Name = "Fiesta" };

Record Equality and ReferenceEquality

Normal C# class object use reference equality when comparing two objects, as shown below and as expected with C# the two objects are not equal.

public class Vehicle
{
    public string Name { get; set; }
    public string Manufacturer { get; set; }
} 

var mondeo = new Vehicle { Name = "Mondeo", Manufacturer = "Ford" };
var anotherMondeo = new Vehicle { Name = "Mondeo", Manufacturer = "Ford" };

var areEqual = mondeo.Equals(anotherMondeo); //false

Records, like structs do not use reference equality, and instead use value equality, comparing the values of the properties in the record.

public record Manufacturer
{
    public string Name { get; init; }
}

public record Vehicle
{
    public string Name { get; init; }
    public Manufacturer Manufacturer { get; init; }
}

var mondeo = new Vehicle { 
  Name = "Mondeo", Manufacturer = new Manufacturer { Name = "Ford" }
};
var fiesta = mondeo with { Name = "Fiesta" };
var anotherMondeo = fiesta with { Name = "Mondeo" };

var areEqual = Equals(mondeo, fiesta); //false
areEqual = Equals(mondeo, anotherMondeo); //true

Reference equality can still be applied to records by using ReferenceEquals

var areRefEqual = ReferenceEquals(fiesta, anotherMondeo); //false
areRefEqual = ReferenceEquals(mondeo, anotherMondeo); //false

So what is C#, and what next?

C# will always be an object-oriented first language, but with more and more functional flavours appearing in C# becoming a multi-paradigm language like C++? Probably. Will developers put these features to good use? I'm not so sure.

I think we'll see more functional features creep into C#, it makes sense to make C# a flexible, powerful language. And as long as the features don't impact the existing object-oriented features the purists should still be happy.

Andy Blyth

Andy Blyth, an Optimizely MVP (OMVP) and Technical Architect at 26 DX with a keen interest in martial arts, occasionally ventures into blogging when memory serves.

 
Andy Blyth