Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is array co-variance considered so horrible?

Tags:

arrays

c#

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?

like image 833
Michael B Avatar asked Nov 30 '10 19:11

Michael B


3 Answers

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.

like image 115
Eric Lippert Avatar answered Oct 28 '22 05:10

Eric Lippert


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 :(

like image 21
Jon Skeet Avatar answered Oct 28 '22 04:10

Jon Skeet


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.

like image 40
Jon Hanna Avatar answered Oct 28 '22 03:10

Jon Hanna