As one of my languages du jour, I've always had a fondness for C#. It was one of the first high-level languages that I learned in college and it's been part of my daily professional life for the better part of ten years. One of the things that's always been enjoyable about the language is the concept of constant innovation.
Features are constantly being adapted from other languages (looking at you F#, Kotlin, Typescript, and more) to help add features that would be greatly desired, extend existing functionality, or just apply an ample sprinkling of syntax sugar. I thought with the new year just beginning, let's take a look at some of the proposed features that are slated to find their way into the flagship Microsoft language in the near future!
It's worth noting that this is by no means a complete list and everything in this list, as with all things in active development, is subject to change / never happen.
So, let's get into it!
Your Results May Covary
Have you ever been writing a class and found yourself just inventing new methods so that you can return some type from a base class since C# has always restricted you to only return the explicit class itself? This proposal outlines what would be support for covariant return types, which would allow you to override an existing method using a more specific implementation than the original method.
class Animal
{
virtual Animal GenerateFromEnvironment(EnvironmentConfiguration config);
}
Now previously, if you wanted to extend your Animal
class to support something like a Dinosaur
, you'd have to either write some hacky bridge method or throw some logic in there to specifically check for the existence of a Dinosaur. Covariant returns will allow you to still override the same base class / methods, but return the more specific implementations you are looking for your class:
class Dinosaur : Animal
{
// Notice the more specific return type overriding the virtual method
override Dinosaur GenerateFromEnvironment(EnvironmentConfiguration config);
}
A "New" New
Often the var
keyword can be great if you don't want to go to the effort of explicitly defining a type for a variable. You already know what it's going to, so it's convenient to just keep things short.This proposal follows that same rough premise by introducing a new usage for the word 'new', specifically type targeted new expressions, which allows you to completely forego the type specification for constructors when the type is already know.
Let's say you had some complex collection like the following that you wanted to initialize upon declaration:
Dictionary<string, List<string>> words = new Dictionary<string, List<string>>() {
{ "foo", new List<string>(){ "foo", "bar", "buzz" } }
};
This proposal simplifies the use of new expressions since it already knows the types, so instead of using new Dictionary<string, List<string>>()
, you can just use new()
:
Dictionary<string, List<int>> words = new() {
{ "foo", new() { "foo", "bar", "buzz" } }
};
Null Checks Kotlinified No More
No one really likes writing countless null checks. Lines upon lines of if statements with nested exceptions being thrown, it's gross. In a vein similar to the previous feature, this proposal aims to eliminate the need for explicit null checking by allowing an operator !
to be added to a parameter to indicate to the compiler that a given value will not be null.
It would transform a snippet like the following:
int CountWords(string sentence)
{
if (sentence is null)
{
throw new ArgumentNullException(nameof(sentence));
}
// Omitted for brevity
}
Into a much terser, uncluttered form:
// Notice the trailing '!' after the parameter name, which indicates
// it will not be null
int CountWords(string sentence!)
{
// Omitted for brevity
}
Let the Constructor Do the Work
Another proposal that has been or in some phase of discussion since C# 6 has been the idea of primary constructors, a feature that's been found in languages like Typescript, Kotlin, and other languages. The basic idea behind primary constructors is that they would simplify writing class constructors and boilerplate code in general by implicitly creating private fields from the arguments passed in by the constructor itself.
Let's look at an example of a class with a few private, readonly properties:
public class Widget
{
private readonly int _foo;
private readonly WidgetConfiguration _config;
public Widget(int foo, WidgetConfiguration config)
{
_foo = foo;
_config = config;
}
}
The proposal would remove the need for the boilerplate field declarations as the constructor itself would handle creating the arguments passed in:
public class Widget
{
public Widget(int _foo, WidgetConfiguration _config)
{
// If you wanted one of these properties to be publicly accessible, you could define
// and set one of those here, otherwise the arguments will be privately accessible
// as fields.
}
}
If That Wasn't Simple Enough... How About Records?
If you enjoyed the last proposed feature, then you may find yourself doing a double-take with this one. Another proposal that has been toyed with for a while is the concept of records. Records are a simplified form for declaring classes and structs that supports some new features to simplify working with them (such as caller-receiver parameters and with expressions).
public class Widget
{
// Properties (business as usual)
public readonly string Foo;
public readonly string Bar;
// Definition of a With expression, which will allow you to
// easily create instances of a widget from an existing instance
public Widget With(string foo = this.Foo, string bar = this.Bar) => new Widget(foo, bar);
}
We can see this demonstrated below:
var existingWidget = new Widget("foo", "bar");
// Now create a brand new instance with one of the properties changed
var clonedWidget = existingWidget.With(bar: "buzz");
// At this point clonedWidget looks like this: { Foo = "foo", Bar = "buzz" }
Records will also support the use of positional pattern matching along with the use of destruction in tandem to do some things like this:
var widget = new Widget("foo", "bar");
// If the widget has a its Foo property set to "foo", then this condition
// will be met
if (widget is Widget("foo", var bar)) {
// Perform some operation here on the widget, the use of var above will
// function as destruction so you can access it within this scope
Console.WriteLine(bar);
}
Records are one of the more involved features being proposed in C# 9, so if you are curious or want to learn more about them, I'd highly recommend checking out the full proposal on them here, which contains a wide range of examples, use cases, and more.
Switching It Up ... Visual Basic Style?
One of the very few things that you'll see come up from a developer that has recently switched from Visual Basic to C# is the switch statement. Despite C# undergoing nearly nine entire revisions, support for comparison operations within switch statement was never supported (although pattern matching comes close), this proposal aims to remedy that.
var internalTemperature = GetInternalTemp(yourDinner);
switch(internalTemperature)
{
case <= 110:
// Very rare
case <= 125:
// Rare
case 125 to 135:
// Perfect
case <= 145:
// Medium
case <= 155:
// What are you doing?
case > 155:
// What have you done...
}
But Wait - There's More!
The features that were covered in this post are some of the more practical ones that might see everyday use, but these were by no means all of the currently proposals. I'd encourage you, if you are interested in learning more about what else might be on the menu at the related milestone for C# 9 on GitHub.
There you'll find tons of additional features that weren't mentioned here such as:
- Discriminated Unions
- Nullable-Enhanced Common Type
- Defer Statements
- Lambda Discard Parameters
- Native-Sized Number Types
Finally - the C# language is open-source and is always looking for contributors, opinions, and any folks that love the language to share their thoughts on what could make it better.
Top comments (1)
Yeah, I agree.
Iβve been spending a good bit of my time writing Kotlin recently, which went all in on this type of decoration/safety, but at the compiler-level. Itβs handy since the declarations will guide use your decision making in what can/cannot be null and the compiler will put its foot down and break the build if it suspects any funny business.
I think itβs one of the weakest features out of the bunch and is really just syntactic sugar.