I originally posted an extended version of this post on my blog a couple of weeks ago.
C# is a language in constant evolution. It has changed a lot since its initial versions in the early 2000's. Every version brings new features to write more concise and readable code. These are my 5 favorite C# features and how it was working before them. Well, I'm not including LINQ in this top, I have a whole guide about LINQ with examples.
1. String interpolation: $"Hello, {name}"
Before with string.Format()
, we could miss a parameter or add them in the wrong order. If we forgot a parameter, we will get a FormatException
.
With string interpolation, we can inline variables directly in the string we want to build. To use string interpolation, before the opening quote of your string, let's add $
and wrap our variables around {}
.
Before with string.Format()
,
string.Format("Hello, {0} {1}", title, name);
But, if we forgot to add one parameter,
string.Format("Hello, {0} {1}", title/*, I forgot to add the name parameter*/);
// ^^^
// System.FormatException:
// Index (zero based) must be greater than or equal to zero and less than the size of the argument list.
After with string interpolation,
$"Hello, {title} {name}";
Now, it's clearer if we're missing a parameter or if we have them in the wrong order.
2. Null-conditional (?.) and null-coalescing operators (??)
Starting from C# 6.0, we have two new operators: null-conditional ?.
and null-coalescing ??
operators. These two new operators helps us to get rid of null values and the evil NullReferenceException
.
With the null-conditional ?.
operator, we access a member's object if the object isn't null. Otherwise, it returns null.
The null-coalescing ??
operator evaluates an alternative expression if the first one is null.
Before,
string name = ReadNameFromSomewhere();
if (name == null)
{
name = "none";
}
else
{
name.Trim();
}
After,
string name = ReadNameFromSomewhere();
name?.Trim() ?? "none";
It executes Trim()
only if name
isn't null. Otherwise, name?.Trim()
returns null
. But, with the ??
operator, the whole expression returns "none".
3. out variables
We can inline the variable declaration next to the out
keyword using the var
keyword.
Before,
int count = 0;
int.TryParse(readFromKey, out count);
After,
int.TryParse(readFromKey, out var count);
Instead of declaring a variable, we can use discards _
to ignore the output value. Do you know discards from other languages? Now, C# have them too. For example,
int.TryParse(readFromKey, out _);
I'm not a bit fan of methods with out
references. But, with this feature I like them a bit more. I prefer tuples.
4. Tuples
Speaking of tuples...Now we can access tuple members by name. We don't need to use Item1
or Item2
anymore.
We can declare tuples wrapping its members inside parenthesis. For example, to declare a pair of coordinates, it would be (int X, int Y) origin = (0, 0)
.
We can use named members when declaring methods and deconstructing returned values.
Before,
Tuple<string, string> Greet() { }
var greeting = Greet();
var name = greeting.Item1;
After,
(string Salutation, string Name) Greet() { }
var greeting = Greet();
var name = greeting.Name;
Even better,
(string Salutation, string Name) Greet() { }
var (Salutation, Name) = Greet();
Again, we can use discards when deconstructing tuples. Like this,
(_, string name) = Greet();
5. Pattern matching
With pattern matching, we have more flexibility in control flow structures like switch
and if
. Let's see a couple of examples.
On one hand, we can avoid casting types inside if
statements.
Before without pattern matching, we needed to cast types,
var employee = CreateEmployee();
if (employee is SalaryEmployee)
{
var salaryEmployee = (SalaryEmployee)employee;
DoSomething(salaryEmployee);
}
After, with pattern matching, we can declare a variable in the condition,
if (employee is SalaryEmployee salaryEmployee)
{
DoSomething(salaryEmployee);
}
On another hand, we can use a when
clause inside switch
.
Before, we had to rely on if
statements inside the same case
, like this
var employee = CreateEmployee();
switch (employee)
{
case SalaryEmployee salaryEmployee:
if (salaryEmployee.Salary > 1000)
{
DoSomething(salaryEmployee);
}
else
{
DoSomethingElse(salaryEmployee);
}
break;
// other cases...
}
Now, with pattern matching, we can have separate cases,
var employee = CreateEmployee();
switch (employee)
{
case SalaryEmployee salaryEmployee when salaryEmployee.Salary > 1000:
DoSomething(salaryEmployee);
break;
case SalaryEmployee salaryEmployee:
DoSomethingElse(salaryEmployee);
break;
// other cases...
}
I found it more readable this way. Let's keep the conditional case before the one without conditions.
Voilà! These are only five of my favorites C# features. It was kind of hard to choose only 5 of them. There've been a lot of changes in the latest versions of the C# language.
Which ones didn't you know about? Which ones you use most often? What features would you like to see in future versions? I would like to see discriminated unions in C#.
Hey, there! I'm Cesar, a software engineer and lifelong learner. Visit my Gumroad page to download my ebooks and check my courses.
Happy coding!