Type Inference
The first C# feature on our list is type inference. In a nutshell, type inference is the ability of the compiler to know the type of a given value without the need for the developer to explicitly declare it. Here are some examples:
1 2 3 4 5 6 |
var someInt = 10; // inferred as int var today = DateTime.Today; // inferred as DateTime var f = new FooBar(); // inferred as FooBar var dateForPresentation = today.ToString("dd/MM/yyyy"); // inferred as string var n = int.Parse(Console.ReadLine()); // inferred as int var numbers = new[] { 1, 2, 3 }; // inferred as int[] |
As you can see from the examples above, type inference works not only when the right-hand side is a literal value. The compiler is also able to infer the type based on the return type of a method or property.
Anonymous Types
Anonymous types are a convenient way to encapsulate a set of properties into an object without having to explicitly declare a class beforehand. Let’s see a quick example:
1 2 3 4 5 |
var person = new { Name = "Alice", Age = 25}; Console.WriteLine("The person is called {0} and is {1} years old.", person.Name, person.Age); |
In the code above, we declare a new anonymous type, which will contain the Name and Age properties, respectively. And we immediately assign it to the person variable.
Can you see how vital type inference is to this feature? The compiler must infer the types of the properties themselves but also the type of the newly created anonymous object as well. This feature would’ve been impossible without type inference.
Properties as a Language Construct
One of the fundamental concepts in OOP is encapsulation. A consumer should not concern itself with the implementation details of the object it’s accessing. Instead of exposing public fields directly, we should use methods for setting the values of our fields—and also retrieving them. These are called, not surprisingly, getters and setters, or even mutator methods. Writing a lot of those gets old fast, though. C# allows you to avoid that by offering the same pattern as a language construct: properties.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Book { private string title; private string author; public string Title { get { return title;} set { title = value; } } public string Author { get { return author;} set { author = value; } } } |
The class would then be used like this:
1 2 3 |
var book = new Book(); book.Title = "Ana Karenina"; book.Author = "Leo Tolstoy"; |
Automatic Properties
A typical usage pattern of properties in C# is to only assign to or read from a private field, without performing any additional logic. People do this a lot, especially when coding DTOs (data transfer objects). The C# language not only recognizes this pattern but also makes it a first-class citizen! So instead of writing code like this:
1 2 3 4 5 6 7 8 9 10 |
public class Person { private string name; public string Name { get { return name; } set { name = value; } } } |
You could simplify it to this:
1 2 3 4 |
public class Person { public string Name { get; set; } } |
The compiler then generates a private backing field for your class automatically! Pretty neat, huh?
Conditional Operator (?)
This C# feature is pretty common in other languages: the conditional operator, sometimes also called the ternary conditional operator. You can think of it as a shorter version of an if statement that returns a value. With it, you can write code that assigns a value based on a condition—same as you would with an if statement, but more shortly.
Here’s an example using an if statement:
1 2 3 4 5 6 7 8 9 10 |
var grade = GetGrade(); if (grade >= 7.0) { result = "Satisfactory"; } else { result = "Unsatisfactory"; } |
And now the same example, but this time using the conditional operator:
1 2 |
var grade = GetGrade(); result = grade >= 7.0 ? "Satisfactory" : "Unsatisfactory"; |
Null Coalesce Operator (??)
Here’s a familiar pattern developers use when dealing with nulls: to check a variable for null before assigning and to use a default value if it turns out to be null. It looks something like this, in practice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Author { public string Name { get; private set; } public IEnumerable<Book> Books { get; private set; } public Author(string name, IEnumerable<Book> books) { Name = name; if (books == null) Books = new List<Book>(); else Books = books; } } |
In the code above, we verify if the Books parameter is null. If that’s the case, we instantiate a new list and assign it to the Books property, to avoid a nasty NullReferenceException. Pretty standard stuff, right?
It’d be possible for us to simplify the code above a little bit using the ternary conditional operator, which you already know. But since you already know that one, I’ll cut to the chase and show you the C# feature that can simplify the code even further: the null coalesce operator. Check it out:
1 2 3 4 5 |
public Author(string name, IEnumerable<Book> books) { Name = name; Books = books ?? new List<Book>(); } |
In the code above, we use the ?? operator, a.k.a. the null coalesce, operator, to check the Books parameter. If it’s not null, its value is assigned to the Books property. Otherwise, the value in the right-hand side (in our case, a new list of Book instances) gets assigned to the property instead.
Throw Expression
Here’s another common pattern that you’ve likely done as a C# developer that involves nulls: checking a parameter and throwing an exception if it turns out it’s null. You’d usually do it like this:
1 2 3 4 5 6 7 8 9 10 11 |
public LibraryService(IBookRepository bookRepository, IAuthorRepository authorRepository) { if (bookRepository == null) throw new ArgumentNullException("bookRepository"); if (authorRepository == null) throw new ArgumentNullException("authorRepository"); this.bookRepository = bookRepository; this.authorRepository = authorRepository; } |
Sure, it’s not the worst code ever written, but it’d be nice if we could simplify it somehow. Well, another C# feature to the rescue! What follows is the simplified version of the code, using throw expressions along with the null coalesce operator:
1 2 3 4 5 |
public LibraryService(IBookRepository bookRepository, IAuthorRepository authorRepository) { this.bookRepository = bookRepository ?? throw new ArgumentNullException("bookRepository"); this.authorRepository = authorRepository ?? throw new ArgumentNullException("authorRepository"); } |
From 11 lines down to five. Not that bad, right?
Nameof
You might’ve noticed that the code in the second example still has room for trouble, despite being shorter and simpler than the previous version. Why? Simple: we’re hardcoding the parameter name when throwing. If the names of the parameters ever change (spoiler alert: everything changes in software) and we forget to alter the string we’ve hardcoded, the clients of our code might become confused reading stack traces in the future.
But don’t worry: there’s a C# feature for that. And it’s called nameof. As its name suggests, it obtains the name of a variable, type, or member. Take a look at some examples:
1 2 3 4 |
Console.WriteLine(nameof(DateTime)); // prints 'DateTime' Console.WriteLine(nameof(int.MinValue)); // prints 'MinValue' var x = 10; Console.WriteLine(nameof(x)); // prints 'x' |
The code from the previous example, with the use of the nameof feature, would look like this:
1 2 3 4 5 |
public LibraryService(IBookRepository bookRepository, IAuthorRepository authorRepository) { this.bookRepository = bookRepository ?? throw new ArgumentNullException(nameof(bookRepository )); this.authorRepository = authorRepository ?? throw new ArgumentNullException(nameof(authorRepository)); } |
Null-Conditional Operator (?.)
The next C# feature on our list also has to do with nullability, and it’s called the null-conditional operator. Consider the code below:
1 |
string parentCommitAuthor = commit.Parent.Author; |
Let’s assume commit will never be null. But if you know your Git, you’ll know that not all commits have parents. So Parent could be null. Before we go and check that, think about the amount of code we’d have to write to check, say, five properties. And of course, Author could be null as well.
So, the C# feature known as the null-conditional operator can help us out here. It checks a member for null and only allows the access to happen if it’s not null. Otherwise, the whole expression is evaluated as null. It might sound a bit complicated, but it’s not. Here are some quick examples:
1 2 3 |
int? length = someString?.Length; // if it's null, the whole expression returns null, so the type of length has to be nullable DateTime? dateOfBirth = employee?.DirectSupervisor?.DateOfBirth; // you can use the operator in chains string shortDateOfBirth = employee?.DirectSupervisor?.DateOfBirth?.ToString("d") ?? "invalid date"; |
Did you notice how in the last example we used the feature along with the null coalesce operator to quickly assign a default value? Interesting, right? We could apply the same technique to the GetInfoAboutCommitParent example:
1 2 3 4 5 |
public void GetInfoAboutCommitParent(Commit commit) { string parentCommitAuthor = commit?.Parent?.Author ?? string.Empty; // more code } |
I really like the way some C# features compound together to allow developers to write clean and concise code like that.
Now It’s Back To You
In today’s post, we’ve covered some of the best features the C# language has to offer. Of course, the list is highly subjective, so we’d like to hear your opinion as well. Did your favorite C# feature make it to the list? Would you have suggestions? Leave your opinion in the comments box below and let’s take it from there.