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.