12

While reading a section of an article about covariance and contravariance at Wikipedia, I ran into the following, bolded sentence:

First consider the array type constructor: from the type Animal we can make the type Animal[] ("array of animals"). Should we treat this as

  • Covariant: a Cat[] is a Animal[]
  • Contravariant: a Animal[] is a Cat[]
  • or neither (invariant)?

If we wish to avoid type errors, and the array supports both reading and writing elements, then only the third choice is safe. Clearly, not every Animal[] can be treated as if it were a Cat[], since a client reading from the array will expect a Cat, but an Animal[] may contain e.g. a Dog. So the contravariant rule is not safe.

Conversely, a Cat[] can not be treated as a Animal[]. It should always be possible to put a Dog into a Animal[]. With covariant arrays this can not be guaranteed to be safe, since the backing store might actually be an array of cats. So the covariant rule is also not safe—the array constructor should be invariant. Note that this is only a issue for mutable arrays; the covariant rule is safe for immutable (read-only) arrays.

I understand the concept; I just want an example of how this "cannot be guaranteed to be safe" in C#.

Mike Perrenoud
  • 66,820
  • 29
  • 157
  • 232
wjmolina
  • 2,625
  • 2
  • 26
  • 35
  • http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx – SLaks Jul 12 '13 at 16:05
  • 1
    I dont know why this is closed as a duplicate of the linked question. A more related q: [why-is-array-co-variance-considered-so-horrible](http://stackoverflow.com/questions/4317459/why-is-array-co-variance-considered-so-horrible) – nawfal May 29 '14 at 15:23

2 Answers2

24

It's not safe at compile time. In other words, there's code which is legal by the language rules, but fails at execution time, without any explicit casting to give a big warning sign of "this might fail". The CLR makes sure that only valid writes succeed at execution time. For example:

string[] strings = new string[1];
object[] objects = strings;
objects[0] = new object();

That will throw an exception (ArrayTypeMismatchException) at execution time. The alternative would have been to allow it at execution time, at which point strings[0] would have been a reference to a non-string object, which would clearly be bad.

See also recent blog posts:

snipsnipsnip
  • 2,268
  • 2
  • 33
  • 34
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • When you say, `The CLR makes sure it's safe at execution time.`, are you referring to the fact that it throws an exception? That's it's way of ensuring it's safe at execution time? – Mike Perrenoud Jul 12 '13 at 16:09
  • @MichaelPerrenoud: Yes, exactly. I'll edit to clarify that. – Jon Skeet Jul 12 '13 at 16:11
  • Fantastic, thank you, I just wanted to make sure I understood! – Mike Perrenoud Jul 12 '13 at 16:11
  • 1
    @JonSkeet I tested the your code it throws ArrayTypeMismatchException. but i put a breakpoint and manually set objects[0] = new object() through DebuggerVisualiser it doesn't throw any error and it works. later checking strings[0].GetType() returns System.Object – Sriram Sakthivel Jul 12 '13 at 16:22
  • @Johnbot could you pls elaborate? how debugger sets value in an array? if it uses `SetValue` method it will throw exception – Sriram Sakthivel Jul 14 '13 at 14:37
  • @SriramSakthivel You are correct. I should have tested it before commenting. – Johnbot Jul 14 '13 at 15:18
  • The link to the blog post of yours seem to be broken. (Couldn't search with the term 'covariance' either) Would love to take a read if the link can be fixed :^) – Gnbrkm41 Jun 02 '19 at 22:40
1

I think what they're trying to say is:

Dog dog = new Dog();
Cat[] cats = new Cat[] { catOne, catTwo, catThree };
Animal[] animals = cats;
animals.Add(dog);

Line 3 of this code cannot be legal because you SHOULD always be able to do line 4 (Adding a Dog to an array of Animals). But if line 3 was legal, then line 4 would not be legal (because you can't add a Dog to an array of Cats).

Tim
  • 14,999
  • 1
  • 45
  • 68
  • 2
    You describe a way that the C# team _could have_ implemented this - but in reality, line 3 _does_ compile, and line 4 is caught at runtime (technically, line 4 doesn't compile because there's no Add method for an array --- but animals[0] = dog would be caught at runtime.) – Hutch Oct 30 '15 at 20:41