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

0
55

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.

How to do more in C#:

Source