📜 ⬆️ ⬇️

New features in C # 4.0. Part 3: Covariance of generalizations

When generic came to us with C # 2.0, they became one of the best features in this language. Those who have ever created classes of strongly typed collections in C # 1.0 know how much they simplified our lives and reduced the amount of code. The only problem was that generic types did not follow the same inheritance rules that were valid for ordinary types.

Let's start with the definition of two simple classes that we will use in this article:
public class Shape { } public class Circle : Shape { } * This source code was highlighted with Source Code Highlighter .
  1. public class Shape { } public class Circle : Shape { } * This source code was highlighted with Source Code Highlighter .
  2. public class Shape { } public class Circle : Shape { } * This source code was highlighted with Source Code Highlighter .
  3. public class Shape { } public class Circle : Shape { } * This source code was highlighted with Source Code Highlighter .
  4. public class Shape { } public class Circle : Shape { } * This source code was highlighted with Source Code Highlighter .
  5. public class Shape { } public class Circle : Shape { } * This source code was highlighted with Source Code Highlighter .
  6. public class Shape { } public class Circle : Shape { } * This source code was highlighted with Source Code Highlighter .
public class Shape { } public class Circle : Shape { } * This source code was highlighted with Source Code Highlighter .
This is a classical class hierarchy, which so far does not do anything concrete, but we don’t need it now. Now we define a dummy class, which can be a container for any type.
  1. public interface IContainer <T>
  2. {
  3. T GetItem ();
  4. }
  5. public class Container <T>: IContainer <T>
  6. {
  7. private T item;
  8. public Container (T item)
  9. {
  10. this .item = item;
  11. }
  12. public T GetItem ()
  13. {
  14. return item;
  15. }
  16. }
* This source code was highlighted with Source Code Highlighter .
We have a hierarchy and a container. Now let's take a look at what we can’t do in the current version of C # - 3.0.
  1. static void Main ( string [] args)
  2. {
  3. IContainer <Shape> list = GetList ();
  4. }
  5. public static IContainer <Shape> GetList ()
  6. {
  7. return new Container <Circle> ( new Circle ());
  8. }
* This source code was highlighted with Source Code Highlighter .
We have a GetList method, in which the return type is defined as IContainer <Shape>, and it returns the Container <Circle>. Since Circle is inherited from Shape, and Container implements the IContainer interface, this may seem to work. But, you guessed it, C # 3.0 is not capable of this.

In C # 4.0, we have a way to make it work — we just need to add the out keyword to the type parameter in our IContainer interface definition (note that covariance in C # 4.0 is limited to interfaces and delegates).
  1. public interface IContainer < out T>
  2. {
  3. T GetItem ();
  4. }
* This source code was highlighted with Source Code Highlighter .
This construct tells the compiler that type T is covariant, which means that any IContainer <T> will accept any type equivalent or more specific than T. As we saw above, the return value type was IContainer <Shape>, but if we put the out parameter to our interface, we can easily return IContainer <Circle> as well. So why did you decide to use the out keyword? This is because when you define a parameter-type as covariant you can only return this type from the interface. For example, such a construction is not allowed:
  1. public interface IContainer < out T>
  2. {
  3. void SetItem (T item);
  4. T GetItem ();
  5. }
* This source code was highlighted with Source Code Highlighter .
But why won't it work? The answer is really very simple - type safety. Let's look at the consequences of what we did:
  1. static void Main ( string [] args)
  2. {
  3. IContainer <Shape> container = new Container <Circle> ();
  4. SetItem (container);
  5. }
  6. public static void SetItem (IContainer <Shape> container)
  7. {
  8. container.SetItem ( new Square ()); // BOOM !!!
  9. }
* This source code was highlighted with Source Code Highlighter .
Since T is covariant and, therefore, we can assign a Container <Circle> to a variable of type IContainer <Shape>, passing it further to our static method SetItem, which accepts a parameter of type IContainer <Shape> and then we take this parameter and try to add variable of type Square into it. It seems that everything is correct - the type of the IContainer <Shape> parameter and this gives us the right to add Square to it. Wrong. This expression will “explode” as we try to add Square to the container that the Circle holds. Therefore, with the out keyword, they limited the covariance to only one direction.
')
You are probably wondering how this is all implemented in CLR 4.0? There is no need for implementation. Generic types worked in CLR 2.0 and they already allowed this behavior. Since C # tried to preserve type safety, we were not allowed to do that, but the CLR does it on time. And, a small remark - arrays in C # allow this behavior, so try. I hope you enjoyed this article and new in this series is just around the corner!

Translation of article C # 4.0 New Features Part 3 - Generic Covariance

Crosspost from my blog

Source: https://habr.com/ru/post/43938/


All Articles