Covariance in generic interfaces. The out keyword
Contents
- 1. Covariance. Features of using in generic interfaces. The out keyword
- 2. An example showing how the covariance mechanism works
- 3. Figure explaining covariance
- 4. Inheritance of covariant interfaces
- 5. Restrictions on the covariance
- Related topics
Search other resources:
1. Covariance. Features of using in generic interfaces. The out keyword
Starting with version 4.0, an extension was introduced to the C# language that allows you to apply covariance and contravariance for the generic type parameter T in interfaces and delegates.
Covariance makes it possible to return a derived type from the base type that is specified in the type parameter.
To indicate to the compiler that the type T supports covariance, the out keyword is used in the following form
out T
Thus, the general form of declaring a generic interface that supports covariance is
interface ICoVarInterface<out T> { // Declaring methods that return type T ... }
here
- ICoVarInterface – the name of the generic interface.
As you can see from the above declaration, the type T in the interface supports covariance because it is preceded by the out keyword.
In the ICoVarInterface<T> interface, you need to declare only methods that return type T. The general form of declaring such a method is as follows
T MethodName(parameters);
here
- MethodName – method’s name;
- parameters – list of method parameters. Parameters can be of any type other than the generic type T.
After declaring an interface, you can declare a class. The declaration of a class that implements the ICoVarInterface interface is carried out in the usual way
class CoVarClass<T> : ICoVarInterface<T> { ... // Implementation of methods of ICoVarInterface interface // ... ... }
The above interface-class pair support covariance. Now this pair can be applied to some hierarchy of classes that will act as type T. If two classes are BaseClass, DerivedClass form a hierarchy
class BaseClass { ... } class DerivedClass : BaseClass { ... }
then when declaring a reference to the generic interface ICoVarInterface<out T>, the type of the base class is always specified as a parameter of type T
ICoVarInterface<BaseClass> refI;
You can create an instance of the CoVarClass<T> class in the usual way, specifying the base class BaseClass as a type parameter, more precisely an instance of the base class
BaseClass objBase = new BaseClass(...); // the instance of base class refI = new CoVarClass<BaseClass>(objBase);
Also, due to covariance, you can specify the type parameter as an instance of a derived class
DerivedClass objDerived = new DerivedClass(...); // the instance of derived class refI = new CoVarClass<DerivedClass>(objDerived); // works - covariance is provided
If the out keyword is removed from the ICoVarInterface declaration
interface ICoVarInterface<T> { ... }
then it will not be possible to create an instance of the CoVarClass class in which the derived class DerivedClass is the type of T
DerivedClass objDerived = new DerivedClass(...); // derived class instance refI = new CoVarClass<DerivedClass>(objDerived); // here is an error - there is no covariance
⇑
2. An example showing how the covariance mechanism works
The example declares:
- the generalized interface IMyCoVarInterface<out T>, which takes as a parameter the type T. This type T supports covariance thanks to the out modifier;
- a generic class MyClass<T> that implements the IMyCoVarInterface<out T> interface. In a class, the type T also supports covariance because the underlying interface supports covariance;
- two classes A, B which form a hierarchy.
The IMyCoVarInterface<out T> interface declares a single GetT() method that returns a reference of type T.
The MyClass<T> class contains the following elements:
- the internal field value of the generic type T. In what follows, instead of the type T, one of the instances of classes A and B, which form a hierarchy, will be substituted;
- a one-parameter constructor that initializes value;
- the Get() method, which accordingly implements the interface method.
To demonstrate covariance, the example created a hierarchy of two classes A and B. In the base class A, the following components are declared:
- internal field a, which is class data;
- constructor with one parameter, initializing field a;
- virtual Get() method for accessing the internal value field.
In inherited class B, the following elements are declared:
- internal field b;
- constructor with two parameters. This constructor initializes field a of the base class and field b of this class;
- an overridden Get() method to access the b field.
using System; namespace ConsoleApp19 { // Interface covariance. // Determined by the out keyword. public interface IMyCoVarInterface<out T> { // Method that returns type T T GetT(); } // A class that implements a covariant interface class MyClass<T> : IMyCoVarInterface<T> { // Internal field T value; // Constructor public MyClass(T _value) { value = _value; } // Method declared in interface IMyCoVarInterface<T> public T GetT() { return value; } } // Some class hierarchy: A - base class, B - derived class. // Base class class A { // Class A data int a; // Constructor public A(int _a) { a = _a; } // Access method public virtual int Get() { return a; } } // Derived class class B : A { // Class B data int b; // Constructor public B(int _a, int _b) : base(_a) { b = _b; } // Access method public override int Get() { return b; } } class Program { static void Main(string[] args) { // Demonstration of covariance. // 1. Declare a reference to the interface, // which receives as a parameter of the type the name of the base class A IMyCoVarInterface<A> refI; // 2. Create an instance of base class A A objA = new A(25); // 3. Create an instance of the class MyClass, in which the type is class A refI = new MyClass<A>(objA); // it works Console.WriteLine("objA.value = " + refI.GetT().Get()); // 4. Create an instance of the derived class B B objB = new B(30, 40); // 5. Assign an instance of the class MyClass to the refI reference, // in which the type is class B refI = new MyClass<B>(objB); // works due to covariance Console.WriteLine("objB.value = " + refI.GetT().Get()); // 40 Console.ReadKey(); } } }
Let’s explain some code snippets. In the main() function, two instances of objA and objB are created, respectively of classes A and B
... // 2. Create an instance of base class A A objA = new A(25); ... // 4. Create an instance of the derived class B B objB = new B(30, 40);
These instances are substituted as placeholder types when creating an instance of the MyClass class
... // 3. Create an instance of the class MyClass, in which class A acts as a type refI = new MyClass<A>(objA); ... // 5. Assign an instance of the class MyClass to the refI reference, // in which the type is class B refI = new MyClass<B>(objB); // works due to covariance
Instantiation in which the placeholder type is class A needs no explanation as the refI is declared for type A
... // 1. Declare an interface reference // that receives as a parameter of type the name of the base class A IMyCoVarInterface<A> refI; ...
And in order to create an instance of class B, it is imperative that when declaring an interface for type T, the out modifier is specified, as in our case
// out - covariance support public interface IMyCoVarInterface<out T> { ... }
If you remove the out keyword from the interface declaration, then you will not be able to create an instance of the MyClass<B> class.
// out is absent - covariance is not supported public interface IMyCoVarInterface<T> { ... } static void Main(string[] args) { ... refI = new MyClass<B>(objB); // compilation error, the word out is missing in the interface ... }
After running for execution, the program produces the following result
objA.value = 25 objB.value = 40
⇑
3. Figure explaining covariance
The following figure explains the mechanism for applying covariance for a generic interface. The importance of using the out modifier is emphasized.
Figure 1. Covariance. The out modifier allows you to create an instance of a generic class in which a derived class is a type
⇑
4. Inheritance of covariant interfaces
Covariant interfaces can be inherited. In the most general case, inheritance of two interfaces looks like this
// Inheritance of covariant interfaces. // Determined by the out keyword. interface ICoVarInterface<out T> { ... } // Interface that inherits ICoVarInterface<T> interface ICoVarInterface2<out T> : ICoVarInterface<T> { ... }
As you can see from the snippet, the out modifier is used to ensure covariance in the inherited interface. If you do not specify out, then the inherited ICoVarInterface2 interface will not use covariance (which cannot be said about the base interface).
When declaring the inherited interface ICoVarInterface2<out T>, you do not need to specify the out modifier in the base interface ICoVarInterface<T>.
⇑
5. Restrictions on the covariance
The following restrictions are imposed on covariance.
1. The covariance of the type parameter T extends only to methods that return the type T. For example, a method with the following signature is suitable for implementing covariance
T GetItem();
and the next is not suitable
int Method(T value);
since it does not return type T.
2. The covariance of a parameter of type T cannot be applied to methods that receive at least one parameter of type T.
For example, the following methods are not suitable for implementing covariance
T GetItem(T value); T Calc(int x1, T x2);
since the type T appears in the methods parameters.
3. Covariance is only suitable for reference types. If you try to implement covariance for structures, the compiler will generate an error.
4. When declaring an interface, a covariant type cannot be used as a constraint. For example, you cannot declare an interface in which the covariant type T is restricted to another type
interface IMyInterface<out T> { T GetItem<V>() where V : T; // here's a compilation error }
⇑
Related topics
- Hierarchies of generic classes. Generalized base and derived classes
- Support for contravariance in generic interfaces. The in keyword
⇑