Why is array co-variance considered so horrible?
Asked Answered
I

4

14

In .NET reference type arrays are co-variant. This is considered a mistake. However, I don't see why this is so bad consider the following code:

string[] strings = new []{"Hey there"};
object[] objects = strings;
objects[0] = new object();

Oh ho, this compiles and will fail at runtime. As we tried to stick an object into a string[]. Okay, I agree that stinks, but a T[] extends Array and also implements IList (and IList<T>, I wonder if it implements IList<BaseType>...>. Both Array and IList allow us to make the same horrid mistake.

string[] strings = new []{"Hey there"};
Array objects = strings;
objects.SetValue(new object(),new[]{0});

IList version

string[] strings = new []{"Hey there"};
IList objects = strings;
objects[0] = new object();

The T[] classes are generated by the CLR, and have to include a type check on the equivalent of the set_Item method (arrays don't actually have one).

Is the concern that setting to a T[] has to do the type check at runtime (which violates the type-safety you expect at compile time)? Why is it considered harmful for arrays to exhibit this property when there are equivalent means to shoot yourself in the foot through the provided means above?

Inclement answered 30/11, 2010 at 19:5 Comment(1)
Did you mean objects[0] = new object(); ?Ahn
B
15

Yes, IList and Array allow you to make the same mistake - because they're weakly-typed APIs to start with.

Arrays look like they're strongly typed (at compile time) but in reality they're not. They could so easily have been safe (and faster) but they're not. It's just a wasted opportunity for both performance and compile-time safety :(

Brendon answered 30/11, 2010 at 19:9 Comment(5)
Can the jitter not optimize out the type checks?Inclement
@Michael B: It may be able to in some situations, but not in all. (For example, if it's a sealed type then I think it should be okay to skip the check.) It's more the lost opportunity of compile-time type safety that bothers me, to be honest. APIs which look type-safe but aren't are annoying :(Brendon
I agree that my bigger problem is not the performance as arrays tend to be really fast, and the compile time check is the bigger issue. Actually, what's really horrible is that string[] implements both IList<string> and IList<object> meaning that we get that awful form of covariance transfered down the line.Inclement
People always express discontent with the current state of affairs but I'm left unsure what they'd prefer. In an ideal world, would you prefer (0) CSharp's status quo (1) no array covariance (2) array covariance implemented in a way that actually allows inserting Parent into (Parent[])childArray ? In fact, I always assumed (3) was the status quo of Csharp and was surprised it's not. What about the current implementation of arrays requires throwing an exception at runtime? Id have expected that a Child[] is really just an object[] with additional compile time type information attachedLudhiana
@Bananach: Eek, no. I'd have preferred no array covariance.Brendon
S
25

In .NET reference type arrays are co-variant. This is considered a mistake.

Type-safety breaking array covariance is considered by some people to be a mistake in the design of .NET. It is not so considered by all people. I do not consider it to be a mistake; I consider it to be an unfortunate choice. All design processes involve choices between undesirable alternatives. In this case, the choice was between adding an unsafe implicit conversion that imposes a run-time cost on all array writes, or building a type system that could not easily implement the Java type system. That's a tough choice and the designers of the type system made the best choice they could with the information they had.

That explanation of course is mere question begging; isn't it then simply the case that the designers of Java made a mistake? Possibly yes, possibly no; likely the designers of Java also faced tradeoffs in the design of their type system. Any experts on the history of the development of the Java type system who would like to chime in here on what those tradeoffs were, I'd be interested to know.

I, with the benefit of ten years of hindsight, personally would have preferred it if the designers of the .NET type system had chosen to eschew safety-breaking array covariance. But that doesn't make that choice a "mistake", it just makes it somewhat unfortunate.

Is the concern that setting to a T[] has to do the type check at runtime (which violates the type-safety you expect at compile time)?

Yes. It means that code that looks like it ought to always run successfully can fail at runtime. And it means that correct code has a performance penalty imposed upon it.

Why is it considered harmful for arrays to exhibit this property when there are equivalent means to shoot yourself in the foot through the provided means above?

This is a strange question. The question is essentially "I have two guns already with which I can shoot myself in the foot, so why is it considered harmful for me to shoot myself in the foot with a third?"

The existence of two dangerous patterns that violate type safety does not make a third such pattern any less dangerous.

Language and runtime features that violate type safety are there for those times when you absolutely positively know that what you are doing is safe, even if the compiler doesn't know it. If you don't understand those features well enough to use them safely then do not use them.

Sponson answered 30/11, 2010 at 20:53 Comment(4)
I wish they went with your insight, because it seems like you had foreseen many of these kinds of issues back before the decisions are made. Would make more sense. But I guess they generally go with the opinion of the majority I guess, right?Bellay
@Joan: I by no means foresaw any of this. I am speaking with the benefit of ten years of hindsight.Sponson
As always, your insight is very helpful. I'm trying to build an API that may be used in a late-bound fashion so I'm trying to decide if I should expose something like an object[] which might really be a string[] or expose it as an IList . I want the arrays content to be mutable by an end user. I guess the better question is should I use array covariance or should I not, as it might give the wrong impression to an end user.Inclement
Thanks Eric, yeah I thought you thought about this scenario but the decision went the other way. I see what you mean. But you know what they say: "hindsight is always better than foresight" :OBellay
B
15

Yes, IList and Array allow you to make the same mistake - because they're weakly-typed APIs to start with.

Arrays look like they're strongly typed (at compile time) but in reality they're not. They could so easily have been safe (and faster) but they're not. It's just a wasted opportunity for both performance and compile-time safety :(

Brendon answered 30/11, 2010 at 19:9 Comment(5)
Can the jitter not optimize out the type checks?Inclement
@Michael B: It may be able to in some situations, but not in all. (For example, if it's a sealed type then I think it should be okay to skip the check.) It's more the lost opportunity of compile-time type safety that bothers me, to be honest. APIs which look type-safe but aren't are annoying :(Brendon
I agree that my bigger problem is not the performance as arrays tend to be really fast, and the compile time check is the bigger issue. Actually, what's really horrible is that string[] implements both IList<string> and IList<object> meaning that we get that awful form of covariance transfered down the line.Inclement
People always express discontent with the current state of affairs but I'm left unsure what they'd prefer. In an ideal world, would you prefer (0) CSharp's status quo (1) no array covariance (2) array covariance implemented in a way that actually allows inserting Parent into (Parent[])childArray ? In fact, I always assumed (3) was the status quo of Csharp and was surprised it's not. What about the current implementation of arrays requires throwing an exception at runtime? Id have expected that a Child[] is really just an object[] with additional compile time type information attachedLudhiana
@Bananach: Eek, no. I'd have preferred no array covariance.Brendon
S
2

I think your note about IList points to something worth considering here.

It is pretty useful that IList is implemented by arrays. It's also useful that there other collections that implement it.

Now, these days, indeed for the last 5 years, we have often found it more useful (or equally useful and safer) to deal with IList<T>.

Prior to .NET2.0 though, we didn't have IList<T>, we only had IList. Quite a few cases where one might move between arrays and other collections were fiddlier (at best) prior to generics which in many cases now let us move between typed collections and typed arrays with greater confidence.

As such, the arguments in favour of covariant arrays were greater when the relevant decisions were made, than they are now. And that they build on similar decisions in Java when it didn't have generics only adds to this fact.

Summersummerhouse answered 1/12, 2010 at 0:21 Comment(0)
M
0

One cost of array-variance is that assignments to an array of a non sealed reference type are a bit more expensive. But considering that assignments to reference types already do quite a bit related to the GC I guess that cost isn't significant.

Mcclurg answered 30/11, 2010 at 19:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.