In this article, we'll take a look at the new Target-typed expressions. Target-typed means that the compiler will now infer the type from the context; thus, you won't have to specify it explicitly.
Object creation
Let's take a simple class.
public class Cat
{
public Cat(string name)
{
Name = name;
}
public string Name { get; }
}
Since C# 3.0, the compiler can infer the type of a variable from the right side of a statement when you use the var
keyword. Basically, var
will take the type of the expression on the right side dynamically.
Cat c = new Cat("Cat before C#3.0"); // Before C# 3.0
var c2 = new Cat("Since C# 3.0"); // Since C# 3.0
It doesn't make much of a difference for types with a concise name, but it could significantly reduce your code's noise for longer names. C# 9.0 brings the ability to infer the type on the right side instead of the left side with var
. It doesn't seem like much at first, but it enables some code simplifications that were impossible before.
Cat c2 = new("C# 9.0"); // C# 9.0
var c3 = new("Invalid"); // this is invalid
Be careful not to use a target-typed expression in conjunction with var
as the compiler will have no clue from the context to infer the type and it will result in a compilation error.
Fields
As it's impossible to declare a field with the var
keyword, its' where this feature starts to shine. Fields tend to have complex generic types that have long names. Before C# 9.0, it was tedious to repeat those types on both sides of the field's assignment.
private ConcurrentDictionary<string, ObservableList<Cat>> _catsBefore = new ConcurrentDictionary<string, ObservableList<Cat>>();
private ConcurrentDictionary<string, ObservableList<Cat>> _cats = new(); // C# 9.0
Object initializers
It can also be combined with object initializers, which is really useful to initialize a collection without repeating the type on each line.
private List<Cat> _catList = new List<Cat>
{
new Cat("cat1"),
new Cat("cat2"),
new Cat("cat2")
};
// Equivalent in C# 9.0
private List<Cat> _catListC9 = new()
{
new ("cat1"),
new ("cat2"),
new ("cat2")
};
Method's parameter
The compiler is also able to infer the type from the calling context of a method. It will automatically assume the type from the method's parameters.
public void Adopt(Cat c)
{
...
}
public void CallerMethod()
{
this.Adopt(new Cat("my cat"));
// C# 9.0
this.Adopt(new("my cat"));
}
Not only for new
Target-typed expression not only works for new
, but also in other situations where the type can be inferred from the context. A good example is with objects that share a base type. The base type can be determined without casting the objects on the right side to their shared based type.
public abstract class Animal
{
}
public class Dog : Animal
{
public Dog(string breed)
{
Breed = breed;
}
public string Breed { get; }
}
public class Cat : Animal
{
public Cat(string name)
{
Name = name;
}
public string Name { get; }
}
Cat c;
Dog d = new("berny mountain");
Animal a = c ?? d; // This feature as not been implemented yet
Unfortunately, at the time of this writing, this feature has been announced but not yet implemented
As all types 'inherit' from null
, we can apply the same trick to nullable types.
Making all types assignable to null is terribly flawed and is often referred to as the Billion dollar mistake in C#. To learn more about it, check this out: Why null in C# is so bad
bool booleanExpression = false;
int? someInt = booleanExpression ? 0 : null;
Closing
As you can see, this new feature will unlock a ton of new simplifications to your codebase. I like that Microsoft is putting a lot of effort into reducing C#'s verbosity while keeping the language backward compatible. If you want to learn more about C# 9.0 and all the other simplifications coming to C#, take a look at my complete C# 9.0 series.
You can also find all the above code examples and much more on my GitHub repo