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