#572 – Why Array Covariance Is Called Covariance

Array covariance in C# allows you to assign an array of instances of a derived class to a variable whose type is an array of instances of the base class.

Dog[] dogs = hounds;     // Where hounds is Hound[] and Hound is subclass of Dog

Covariance says that the ordering of two elements in a set is preserved after transforming each by the same function.

With array covariance, we can think of the “ordering” as being the fact that the subtype is narrower than the base class, which means that the assignment is allowed due to assignment compatibility.

The covariant function being applied to each type is to create an array of that type.  This “function”, an array of a type, is then covariant because if type T is narrower than type U, then T[] is also narrower than U[], preserving assignment compatibility.

Advertisement

#571 – Covariance in Programming Languages

In programming languages, the idea of covariance has to do with whether the ordering of a set of elements is preserved after calling some function that transforms each element.

Consider a set of elements and a function F that accepts as input a member of the set and returns a member of the set.  I.e. X’ = F(X), where both X and X’ are members of the set.

We describe a function as covariant if preserves the ordering of elements of the set passed to it.  If we have two members of our set, X and Y, and X <= Y, then the function F is covariant if F(X) <= F(Y).

For example, the function F(X) = 2X is covariant with respect to the set of integers.  If X <= Y, then 2X <= 2Y, for any X and Y integer values that you pick.

#570 – Assignment Compatibility for Reference Types

A reference type is assignment compatible if the value being assigned belongs to a type that is either the same as, or is a derived type of, the type of the storage location being assigned to.  You can assign a variable of type T to a storage location of type U if T is a narrower type than U, or is the same type as U.

Hound huck = new Hound("Huckleberry", 55);

// Since Hound is a sub-class of Dog, we can
// assign to a variable of type Dog
Dog dog1 = huck;

In this case, the variable of type Dog will still be pointing to an instance of a Hound.  The assignment doesn’t change anything about the object.

To see this, construct an instance of a Dog directly and then compare the objects.

// This one points to an actual Dog instance
Dog dog2 = new Dog("Just some dog", 2);

#569 – Assignment Compatibility

The idea of assignment compatibility in C# is the idea that you can store a value that has a particular type into a storage location (variable) of a different type without losing any data.  (The conversion is “representation-preserving“).

So we can say that type T is assignment compatible with type U if we can store values of type T into variables of type U.

For value types, a type T will typically be assignment compatible with another type U, if T can represent a subset of the values that U can represent.  T can be thought of as “smaller” or “narrower” than U.

// byte is assignment compatible with ushort
byte n1 = 123;    // byte: 0-255
ushort n2 = n1;   // ushort: 0-65535

As you’d expect, a type is always assignment compatible with itself:

            byte n1 = 123;
            byte n2 = n1;

#568 – Array Covariance

In C#, you can always implicitly convert an instance of a more derived type  to an instance of a base type.

For example, the following is allowed:

Hound huck = new Hound("Huckleberry", 55);

// Since Hound is a sub-class of Dog, we can
// assign to Dog
Dog someDog = huck;

Note that at this point, the someDog variable points to an instance of a Hound, rather than an instance of a Dog.

You can also assign an array of objects of a more derived type to an array of objects of a base type.  This is known as array covariance.  It’s allowed as long as the type of the source array elements is implicitly convertible to the type of the target array elements.

Hound[] hounds = new Hound[2] {
new Hound("Huckleberry", 55),
new Hound("Astro", 50)};

// Allowed because of array covariance
Dog[] dogs = hounds;

#567 – Wider vs. Narrower Types

In a object-oriented programming language like C#, due to inheritance, we often end up with a hierarchy of types.

Types lower down in the diagram above are known as derived classes and the class above them, which they inherit from, is known as their base class.  For example, Working is a derived class with respect to Dog, which is its base class.  Notice that a class can be both a derived class and a base class for another class.  (E.g. Working serves as a base class for Boxer).

We also refer to types higher up in the class hierarchy as wider and types further down as narrower.  The Dog class is wider than the Terrier class in the sense that there are more dogs than there are Terriers.  You can also think of the narrower classes as being more specialized–Terrier is a specific type of Dog.

#566 – Implicit Conversions to Nullable Types

A nullable type represents a type whose value can be either a particular value type or can be the null value.

int i = 12;   // regular int, can't be null

int? j = 22;  // Nullable int, can store an int value
j = null;     // Can also store null value

You can implicitly convert from the corresponding value type to its matching nullable type.  For example, you can convert from an object of type int to an object of type int?

int i = 12;
int? j = i;   // Implicit conversion from int to int?

float f1 = i;   // Implicit conversion from int to float
float? f2 = i;  // Implicit conversion from int -> float -> float?

You can also implicitly convert from the null value to any nullable type.

int? i = null;

#565 – Using an Iterator to Return A Shuffled Sequence

The System.Linq namespace includes an OrderBy method that makes it easy to reorder an enumerable sequence, based on a particular key.  For a key, we can use a newly generated Guid to achieve a random order.  (See Jeff Atwood’s post on Shuffling).

We can encapsulate this shuffling behavior in a method that returns the resulting shuffled sequence as an IEnumerable. We’ll use an iterator in the body of this method to return the shuffled sequence of elements.

        // Two line shuffle
        static IEnumerable<int> Shuffle(List<int> theList)
        {
            foreach (int next in theList.OrderBy(x => Guid.NewGuid()))
                yield return next;
        }

You can now use this method to iterate through the original sequence in a random order.  You can also view this as moving through a newly shuffled sequence.  Note that the original list is not re-ordered.

            List<int> myList = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            foreach (int i in Shuffle(myList))
                Console.WriteLine(i);

#564 – Use the Reverse Method to Iterate Backwards through a Collection

You can use the Enumerable<TSource>.Reverse method on any enumerable object, to iterate backwards through its collection.

Because arrays and collections implement the IEnumerable interface, you can use a foreach statement to enumerate through their elements in a forward-only fashion.

            int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            foreach (int i in nums)
                Console.WriteLine(i);

The default enumerator implemented in types like System.Array and List<T>, however, only allows you to iterate forwards through a collection.  If you instead want to iterate backwards through an array or collection, you can use the Reverse method mentioned above. This method is part of System.Linq and is an extension method that works on any IEnumerable<T> type.

            int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            foreach (int j in nums.Reverse())
                Console.WriteLine(j);

            List<Dog> myDogs = new List<Dog>();
            myDogs.Add(new Dog("Jack", 17));
            myDogs.Add(new Dog("Kirby", 15));
            myDogs.Add(new Dog("Ruby", 1));

            foreach (Dog d in myDogs.Reverse<Dog>())
                Console.WriteLine(d.Name);

#563 – Enumerable Types Can Generate Multiple Enumerators

Each time that the GetEnumerator method in an enumerable type is called, an independent enumerator is generated.  Each enumerator keeps track of its own position in the parent sequence.

In the example below, nested foreach statements each iterate over the same collection.  The outer foreach statement results in one enumerator and the inner foreach statement results in the generation of a new enumerator for each pass of the outer foreach.

In this case, the ability to generate multiple independent enumerators is necessary, so that we can have overlapping foreach statements.

            int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            foreach (int i in nums)
                foreach (int j in nums)
                    Console.WriteLine("{0} x {1} = {2}", i, j, i * j);