Veverke
Veverke

Reputation: 11378

Type Covariance runtime errors

C# 6.0 in a Nutshell by Joseph Albahari and Ben Albahari (O’Reilly).

Copyright 2016 Joseph Albahari and Ben Albahari, 978-1-491-92706-9.

states, at pages 123-124, with regards to type covariance:

Arrays, for historical reasons, array types support covariance. This means that B[] can be cast to A[] if B subclasses A (and both are reference types).

For example:

Bear[] bears = new Bear[3]; 
Animal[] animals = bears; // OK

The downside of this reusability is that element assignments can fail at runtime:

animals[0] = new Camel(); // Runtime error

What is the reason behind such error ? If you assign an instance of Bear to an instance of Animal, a runtime error will be thrown ? I do not see why it should (by allowing such an assignment, the compiler needs to take responsibility in stating "all right, I will let you do with this object everything that an animal can do." Since Bear is an animal, this incurs no problems whatsoever.

I created my own scenario to test the above:

public class X
{
    public int Num { get; set; }

    public void Method_1()
    {
        Console.WriteLine("X");
    }

    public virtual void Method_2()
    {
        Console.WriteLine(Num);
    }
}

public class Y : X
{
    public Y()
    {
        Num = 1000;
    }
}

X[] arrayX = new X[] { new X { Num = 1000 }, new X { Num = 999 }, new X { Num = 51762 } };
Y[] arrayY = new Y[] { new Y { Num = 5 }, new Y { Num = 6 }, new Y { Num = 7 } };

X x = new X { Num = 1000 };
Y y = new Y { Num = 50 };

x = y;

arrayX = arrayY;

arrayX[2] = new Y { Num = 1 };

// will print 5,6,1 - no runtime errors faced
foreach (var e in arrayX)
    Console.WriteLine(e.Num);

I believe the snippet above mimics the book's example - but with my snippet, there are no runtime errors.

What am I missing ? How is animals[0] = new Camel(); supposed to thrown a runtime error, as the book states ?

Upvotes: 1

Views: 213

Answers (4)

Stephen Zeng
Stephen Zeng

Reputation: 2818

For this code:

Animal[] bears = new Bear[3];

If you installed Resharper in your VS, it gives the same warning of:

Co-variant array conversion from Bear[] to Animal[] can cause run-time exception on write operation.

If you use Generic such as IList:

IList<Animal> bears = new List<Bear>();
bears.Add(new Camel());

the first line won't let you compile, which is safer, prevents it from possible run-time exception.

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1502216

What is the reason behind such error?

Because it is trying to store a Camel into an array with a runtime type of Bear[]. An array of type Bear[] can only store references to instances of Bear or subclasses. The compile-time type of Animal[] only says that it might be able to store a Camel reference, and that any reference you get out of the array will definitely be an Animal instance or subclass.

Your example is different. When we strip out all the properties etc (which are irrelevant) you've got:

X[] arrayX = new Y[3];
arrayX[2] = new Y();

That's fine - that's storing a reference to a Y object in an array with an execution-time type of Y[]. No problem.

To demonstrate the same problem as the book, you'd need a third class:

class Z : X {}

X[] arrayX = new Z[3];
arrayX[2] = new Y(); // Bang - can't store a Y reference in a Z[]

Upvotes: 4

Matias Cicero
Matias Cicero

Reputation: 26301

In the following line:

Animal[] animals = bears;

You are just hiding your Bear[] into an Animal[].

Note that I say hiding, because you are not actually creating a new array.

Both bears and animals point to the same Bear[], the only difference is that animals is hiding that reference as Animal[].


The compiler does not know this.

For the compiler, if you want to store a Dolphin, or a Lion on animals, it will let you do so, since all those elements are of type Animal.


The runtime however would complain.

Since animals hides a Bear[], adding a Camel to it will not be allowed.

Although Bear and Camel both inherit from Animal, they are two different types.

Upvotes: 1

mwilczynski
mwilczynski

Reputation: 3082

Your example with X and Y is quite different than the one from book. If you would like to mimic the one from book, create abstract base class X and then make Y and Z derive from it. Then, play around with it. The one from book implies that:

class Program
{
    static void Main(string[] args)
    {
        Bear[] bears = new Bear[3];
        Animal[] animals = bears;
        animals[0] = new Camel(); //will throw on runtime
    }
}

public abstract class Animal { }

public class Camel : Animal { }

public class Bear : Animal { }

As Jon already stated, runtime type of animals will be Bear[] which simply cannot store an instance of Camel. Also please notice, that's a possible error for covariant array conversion, but not gonna happen for other collections like List:

        List<Bear> bearsList = new List<Bear>();
        List<Animal> animalsList = bearsList; //won't compile because of this error
        animalsList[0] = new Camel();

Upvotes: 1

Related Questions