Joydip Kanjilal
Contributor

How to use target typing and covariant returns in C# 9

how-to
Mar 30, 20216 mins
C#Microsoft .NETSoftware Development

Learn two of the most important features in C# 9.0 to make your code more concise, readable, flexible, and efficient.

target bullseyes
Credit: Thinkstock

C# 9 introduced a number of features that allow us to write more efficient and flexible code. In previous articles we examined record types, static anonymous functions, relational and logical patterns, and top-level programs. In this article we’ll look at two more useful features in C# 9 — new target typing capabilities and covariant returns.

Target typing refers to using an expression that gets its type from the context in which it is used, rather than specifying the type explicitly. With C# 9, target typing now can be used in new expressions and with conditional operators. Support for covariant return types in C# 9 allows the override of a method to declare a “more derived” (i.e., more specific) return type than the method it overrides.

To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here. C# 9.0 is available in Visual Studio 2019 16.9 Preview 1 or later, or in the .NET 5.0 SDK.

Create a console application project in Visual Studio

First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Click Create.

We’ll use this .NET Core console application project to work with target typing and covariant returns in the subsequent sections of this article.

Use target typing with new expressions in C# 9

With C# 9 you now have improved support for target typing. There are two new ways in which you can implement target typing — in new expressions and with conditional operators.

When using target-typed new expressions, you need not mention the type you want to instantiate. Let’s understand this with an example. Consider the following class:

public class Author
{
   private int Id { get; set; }
   private string FirstName { get; set; }
   private string LastName { get; set; }
   public Author(int id, string firstName, string lastName)
   {
      Id = id;
      FirstName = firstName;
      LastName = lastName;
   }
}

The following code snippet illustrates how you can use target typing to omit specifying the type when creating an instance of the class shown above.

static void Main(string[] args)
{
    var authors = new List<Author>
    {
        new (1, "Joydip", "Kanjilal"),
        new (2, "Dean", "Jones"),
        new (3, "Steve", "Smith")
    };
   Console.Read();
}

Use target typing with conditional operators in C# 9

With C# 9 it is now possible to infer types using conditional operators. Consider the Person class given below:

public class Person
    {
        private int Id { get; set; }
        private string FirstName { get; set; }
        private string LastName { get; set; }
        public Person(int id, string firstName, string lastName)
        {
            Id = id;
            FirstName = firstName;
            LastName = lastName;
        }
    }

Assume that there are two types of workers — an employee and a consultant. Here are the two classes that represent an employee and a consultant:

public class Employee : Person
    {
        private string Department { get; set; }
        public Employee(int id, string firstName, string lastName,
        string department) :
        base(id, firstName, lastName)
        {
            Department = department;
        }
    }
    public class Consultant : Person
    {
        private int RatePerHour { get; set; }
        public Consultant(int id, int ratePerHour,
        string firstName, string lastName) :
        base(id, firstName, lastName)
        {
            RatePerHour = ratePerHour;
        }
    }

The following code will compile in C# 9, but will not compile in earlier versions of C#:

Employee employee = new Employee(1, “Joydip”, “Kanjilal”, “Development”); Consultant consultant = new Consultant(1, 150, “Joydip”, “Kanjilal”); Person person = employee ?? consultant; //Compilation error prior to C# 9

C# 9 lets you use target-typed conditional operators in ternary statements.

Use covariant return types in C# 9

Covariant return types is a feature that enables you to override a method of a base class with a method in the derived class to return a more specific type. Earlier versions of C# did not allow returning a different type (than its base version) in an overridden method of a derived class. This changes in C# 9.

For example, suppose you have two classes A and B and that the latter extends the former. If you have a virtual or abstract method in class A, you can override it in class B. You can do this because the C# programming language provides support for run-time polymorphism or late binding. However, until C# 9, you could not change the return type of the overridden method in the derived class.

Let’s understand this with an example. Consider the updated version of the Person class given below.

public class Person
    {
        private int Id { get; set; }
        private string FirstName { get; set; }
        private string LastName { get; set; }
        public Person()
        { }
        public Person(int id, string firstName, string lastName)
        {
            Id = id;
            FirstName = firstName;
            LastName = lastName;
        }
        public virtual Person GetPerson()
        {
            return new Person();
        }
    }

Note we’ve added a virtual method and a default constructor. Here we are returning an instance of the Person class from the GetPerson method by calling the Person class’s default constructor.

Now consider the Employee class given below:

public class Employee : Person
    {
        private string Department { get; set; }
        public Employee()
        { }
        public Employee(int id, string firstName, string lastName,
        string department) : base(id, firstName, lastName)
        {
            Department = department;
        }
        public override Employee GetPerson()
        {
            return new Employee();
        }
    }

And we’ve added a default constructor here too. Here an instance of the Employee class is being returned by the GetPerson method that overrides the Employee class. Interestingly, the return type of the GetPerson method is Employee in the Employee class.

Note that the above code would not compile in C# versions before C# 9 because you’re constrained to use the same signature for your overridden methods in a derived class.

Covariance and contravariance are longtime C# features (added with C# 4.0) that provide a polymorphic extension to delegates, arrays, and generics. Covariance enables you to use a more derived type (more specific) than originally specified, while contravariance enables you to use a less derived type (less specific). You can learn more about covariance and contravariance from Microsoft’s online documentation.

Joydip Kanjilal
Contributor

Joydip Kanjilal is a Microsoft Most Valuable Professional (MVP) in ASP.NET, as well as a speaker and the author of several books and articles. He received the prestigious MVP award for 2007, 2008, 2009, 2010, 2011, and 2012.

He has more than 20 years of experience in IT, with more than 16 years in Microsoft .Net and related technologies. He has been selected as MSDN Featured Developer of the Fortnight (MSDN) and as Community Credit Winner several times.

He is the author of eight books and more than 500 articles. Many of his articles have been featured at Microsoft’s Official Site on ASP.Net.

He was a speaker at the Spark IT 2010 event and at the Dr. Dobb’s Conference 2014 in Bangalore. He has also worked as a judge for the Jolt Awards at Dr. Dobb's Journal. He is a regular speaker at the SSWUG Virtual Conference, which is held twice each year.

More from this author