C# and .NET have both been in consistent active development for the last two decades; every year the language gets a set of new useful features. We’ll discuss some of our favorites that we think beginners should know about.
Nullable Reference Types
C# has had nullable value types for a while, such as ”
int? ” which can hold either an
int or the value of
null, as opposed to a traditional
int which would always have a default value of zero. These are useful for a lot of things, including classes meant for JSON deserialization, where not all fields may be present.
However, reference types have always been able to be assigned a
null value, so what’s the point of this new feature from C# 8.0?
Nullable reference types basically enforce a distinction between reference variables that can go null, and reference variables that can’t. It’s a breaking feature that will likely leave your codebase with a lot of warnings, so it’s something you have to manually turn on. Once it’s on, the compiler start to tell the difference between:
string?, which can be null, and retains the “default” behavior from earlier versions, and
string, which cannot be null. It can never be null, because it must be given a default value, and can never be set to null.
This has a lot of interesting effects, but the most important one is an implicit focus on proper
null value handling at a language level. For example, The compiler will yell at you if you try to return a null value for a function that returns a non-nullable value. It will also yell at you if you try to pass a possibly null value into a function that isn’t expecting it.
While that can sound restricting, much like static typing it leads to better and more usable code in the end. We highly recommend enabling it for most projects, and you can read our full guide on them to learn more.
To turn it on, you’ll need to edit your project file. In Visual Studio, right click on your project and click “Edit Project File.” Then turn it on with the following directive:
If you are using the legacy project format, you might need to manually override this with a directive at the top of each file:
Null-Conditional and Null Coalescing Operators
Rather than having to check
if(something == null), C# has an awesome shorthand with null-conditional member access operators. Basically, rather than using a period to access something that may be null, you can use a question mark and a period, which will perform the null check automatically.
You can also use these for calling methods on null objects, or accessing indexes on null arrays. If the object ends up being null, it simply does nothing, and returns a null value.
reference?.field reference?.method(); reference?[N]
Note that the last one does not prevent an IndexOutOfRangeException—it simply accesses the Nth element of a possibly null list.
However, you may have to work with the null values returned by this expression, and to make that easier C# has null-coalescing operators. These can be used to assign an alternative value in the event that an expression (any expression) returns a null value. Essentially, they’re backup values. You can specify them with double question marks:
string value = GetValue() ?? "Backup"
There is also the
??= operator, which functions like
|| in that it won’t evaluate the backup value if the first value returns a proper result.
Ever wanted to return multiple values from a method? With tuples you can, and modern C# has had great language support for them since C# 7.0. Simply return two values surrounded by parentheses and separated by commas, and you can access the individual items within them.
While it’s not required, it is common practice to give these names, such as
(float X, float Y, float Z), rather than accessing them by item numbers.
You can also use tuple deconstruction to dissemble a tuple into multiple component variables.
This is actually quite useful for simple constructors, where you need to set a few fields equal to input arguments. Using tuple deconstruction accomplishes this pretty cleanly:
Constructor Overloading With : this ()
Constructors, like any other method, can be operator overloaded to support many different combinations of parameters. However, since constructors are commonly used to initialize many fields, this can lead to code duplication.
A quick and dirty fix would be to share a “initialize class” method that’s called from all overloaded constructor methods, but if you have nullable reference types enabled, you’ll get nullability warnings for non-nullable fields that are actually getting set, since the compiler isn’t smart enough to understand initialization in impure function calls.
But, there is fix for this, and it’s a little weird. It comes from constructor inheritance, which is another awesome feature that allows you to expand upon the constructor of the base class. It uses the same inheritance syntax, a colon, followed by
base (parameters). This will automatically call the base constructor (before the new one). Note that you still need to put the base constructor’s parameters in the method definition.
The cool part is that you don’t need to use
base; you can actually do the exact same thing with
: this (), which will call a constructor within the class itself. You can use this to specify additional parameters without copying initialization code.
You’ll obviously need to declare the optional fields as nullable, since they are as the base constructor does not support them. But that’s by design here; in this example, the person’s first and last name needs to be set, but the email may or may not be, making it suitable for a nullable type.
Constructors are commonly used to create instances of classes using the
new keyword. In the constructor, you can set up the fields required to initialize the class.
But what about static classes? Well, they actually can use constructors as well. In fact, regular classes can use static constructors to set up their static properties.
However, these don’t exactly run at startup. While the above example looks correct, setting the
startupTime in a static constructor, it’s not guaranteed at runtime because C#, and the MSIL it runs on, is a Just-In-Time compiled language.
JIT compilation only occurs, well, just in time, exactly when the class is needed. This means that the class will sit off in its corner of the assembly, collecting dust until one of its fields or methods are needed. Once it’s needed, the .NET runtime will dust it off, compile it up, and only then invoke the static constructor.
However, the static constructor still runs before anything, even before static fields are set up, and before anything can be referenced. They’re still quite useful when you need them. Alternatively, you can call an initialization method from your application’s startup routine if you need to run something in chronological order.
Generic Type Parameters
You’ve definitely run into these before, though you may not have written one yourself. Generic type parameters allow you to write functions that are type-agnostic, and don’t care about the type being passed to it. The primary example of this is collections; a
List<string> and a
List<int> use the same code, but are passed a different generic type parameter.
Generics are pretty easy to use yourself. Simply add a name for the type variable in brackets in the class or method definition. It’s common practice to use T, or at least names that start with T. For example, a dictionary might have TKey and TValue, two different types.
You can use them in functions too, and if you use the type parameter as an argument type, it can even be inferred automatically.
Generic types create multiple different “types” of the generic class. This means static fields will be separate based on the type of the class, so
List<string> shares no data with
Because of this, you need to pass it a type parameter if you want to reference the class name directly. This can be a problem in some cases, so alternatively, if you need to support multiple types, you can cast to and from
object using a technique called boxing.
Delegates are a way of packaging methods into variables. This is useful for dependency injection, which is a overly fancy name for a simple concept—flexible classes should get some values, called dependencies, from their constructor variables, allowing the user of that class to specify the dependencies at will.
Delegates allow you to do this with functions. You can have the class take any kind of action, and it doesn’t care about the implementation. These are still statically typed—you will have to define the input and output parameters like you would with any function, except mark it with “delegate” and don’t write a body.
You can then assign a function to this variable, and then use the variable to invoke the function. You can do this directly like shown, or you can use
myDelegate.Invoke(), which does the same thing but more verbose.
You can read more about delegates in our guide to using them.
Classes in C# use fields to store data, and properties to expose that data to other classes. Properties are really just a method exposing a field so that you can access it by doing
You can do the same thing for indexing, e.g.
class[index]. This can be used to create custom indexing behaviour. For example, you can create a 2D list from a 1D
List<T> by making a custom indexer that returns a value based on the input arguments.