75

Array is declared:

public abstract class Array
    : ICloneable, IList, ICollection, IEnumerable {

I'm wondering why isn't it:

public partial class Array<T>
    : ICloneable, IList<T>, ICollection<T>, IEnumerable<T> {
  1. What would be the issue if it was declared as a generic type?

  2. If it was a generic type, do we still need the non-generic one or could it derive from Array<T>? Such as

    public partial class Array: Array<object> { 
    
Ken Kin
  • 4,503
  • 3
  • 38
  • 76
  • What would you want out of an `Array` that a `List` does not provide? – D Stanley Jan 14 '13 at 19:24
  • 2
    There would be no problem, but why would you create a class that doesn't add any value? You already have `List`, `Array`, and `T[]`. (by the last example I mean a strongly-typed array, not a "generic" array) – D Stanley Jan 14 '13 at 20:14
  • It does not cause an issue; it is redundant and unnecessary. Does NOT having it cause you an issue? What would it give you that List doesn't already? Or is it just semantics? – D Stanley Jan 14 '13 at 20:39
  • 3
    The questions from D Stanley are irrelevant and miss the point, and his claims are false. Fortunately, correct and relevant information is provided by Virtlink's comprehensive answer. – Jim Balter Apr 07 '16 at 23:04
  • 1
    Array is properly generic in Swift, which I think has borrowed heavily from C#. So C# arrays seem to be a case of a language that is stuck with a premature implementation because so much code is based on the old style. – Mark Patterson Aug 08 '17 at 06:57
  • @MarkPatterson: I'm a bit confused. Why would using generics on the `Array` type be useful? I'd imagine that if you want generics, you probably should be going with an interface-first approach, which predicates the usage of `IList` or `IEnumerable`, to be honest. Not sure how `Array` fits into the picture here. – code4life Dec 02 '17 at 15:32
  • Dang. I resurrected an ancient post without checking the date. Sorry...!!!!! – code4life Dec 02 '17 at 15:34
  • @code4life Thank you for bringing this to my attention of my broken sentence :) – Ken Kin Dec 02 '17 at 15:52

7 Answers7

156

History

What problems would arise if arrays became a generic type?

Back in C# 1.0 they copied the concept of arrays mainly from Java. Generics did not exist back then, but the creators thought they were smart and copied the broken covariant array semantics that Java arrays have. This means that you can pull off things like this without a compile-time error (but a runtime-error instead):

Mammoth[] mammoths = new Mammoth[10];
Animal[] animals = mammoths;            // Covariant conversion
animals[1] = new Giraffe();             // Run-time exception

In C# 2.0 generics were introduced, but no covariant/contravariant generic types. If arrays were made generic, then you couldn't cast Mammoth[] to Animal[], something you could do before (even though it was broken). So making arrays generic would've broken a lot of code.

Only in C# 4.0 were covariant/contravariant generic types for interfaces introduced. This made it possible to fix the broken array covariance once and for all. But again, this would've broken a lot of existing code.

Array<Mammoth> mammoths = new Array<Mammoth>(10);
Array<Animal> animals = mammoths;           // Not allowed.
IEnumerable<Animals> animals = mammoths;    // Covariant conversion

Arrays implement generic interfaces

Why don't arrays implement the generic IList<T>, ICollection<T> and IEnumerable<T> interfaces?

Thanks to a runtime trick every array T[] does implement IEnumerable<T>, ICollection<T> and IList<T> automatically.1 From the Array class documentation:

Single-dimensional arrays implement the IList<T>, ICollection<T>, IEnumerable<T>, IReadOnlyList<T> and IReadOnlyCollection<T> generic interfaces. The implementations are provided to arrays at run time, and as a result, the generic interfaces do not appear in the declaration syntax for the Array class.


Can you use all members of the interfaces implemented by arrays?

No. The documentation continues with this remark:

The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.

That's because (for example) ICollection<T> has an Add method, but you cannot add anything to an array. It will throw an exception. This is another example of an early design error in the .NET Framework that will get you exceptions thrown at you at run-time:

ICollection<Mammoth> collection = new Mammoth[10];  // Cast to interface type
collection.Add(new Mammoth());                      // Run-time exception

And since ICollection<T> is not covariant (for obvious reasons), you can't do this:

ICollection<Mammoth> mammoths = new Array<Mammoth>(10);
ICollection<Animal> animals = mammoths;     // Not allowed

Of course there is now the covariant IReadOnlyCollection<T> interface that is also implemented by arrays under the hood1, but it contains only Count so it has limited uses.


The base class Array

If arrays were generic, would we still need the non-generic Array class?

In the early days we did. All arrays implement the non-generic IList, ICollection and IEnumerable interfaces through their base class Array. This was the only reasonable way to give all arrays specific methods and interfaces, and is the primary use of the Array base class. You see the same choice for enums: they are value types but inherit members from Enum; and delegates that inherit from MulticastDelegate.

Could the non-generic base class Array be removed now that generics are supported?

Yes, the methods and interfaces shared by all arrays could be defined on the generic Array<T> class if it ever came into existence. And then you could write, for example, Copy<T>(T[] source, T[] destination) instead of Copy(Array source, Array destination) with the added benefit of some type safety.

However, from an Object-Oriented Programming point of view it is nice to have a common non-generic base class Array that can be used to refer to any array regardless of the type of its elements. Just like how IEnumerable<T> inherits from IEnumerable (which is still used in some LINQ methods).

Could the Array base class derive from Array<object>?

No, that would create a circular dependency: Array<T> : Array : Array<object> : Array : .... Also, that would imply you could store any object in an array (after all, all arrays would ultimately inherit from type Array<object>).


The future

Could the new generic array type Array<T> be added without impacting existing code too much?

No. While the syntax could be made to fit, the existing array covariance could not be used.

An array is a special type in .NET. It even has its own instructions in the Common Intermediate Language. If the .NET and C# designers ever decide to go down this road, they could make the T[] syntax syntactic sugar for Array<T> (just like how T? is syntactic sugar for Nullable<T>), and still use the special instructions and support that allocates arrays contiguously in memory.

However, you would lose the ability to cast arrays of Mammoth[] to one of their base types Animal[], similar to how you can't cast List<Mammoth> to List<Animal>. But array covariance is broken anyway, and there are better alternatives.

Alternatives to array covariance?

All arrays implement IList<T>. If the IList<T> interface were made into a proper covariant interface then you could cast any array Array<Mammoth> (or any list for that matter) to an IList<Animal>. However, this requires the IList<T> interface to be rewritten to remove all methods that might change the underlying array:

interface IList<out T> : ICollection<T>
{
    T this[int index] { get; }
    int IndexOf(object value);
}

interface ICollection<out T> : IEnumerable<T>
{
    int Count { get; }
    bool Contains(object value);
}

(Note that the types of parameters on input positions cannot be T as this would break covariance. However, object is good enough for Contains and IndexOf, who would just return false when passed an object of an incorrect type. And collections implementing these interfaces can provide their own generic IndexOf(T value) and Contains(T value).)

Then you could do this:

Array<Mammoth> mammoths = new Array<Mammoth>(10);
IList<Animals> animals = mammoths;    // Covariant conversion

There is even a small performance improvement because the runtime would not have to check whether an assigned value is type compatible with the real type of the array's elements when setting the value of an element of an array.


My stab at it

I took a stab at how such an Array<T> type would work if it were implemented in C# and .NET, combined with the real covariant IList<T> and ICollection<T> interfaces described above, and it works quite nicely. I also added the invariant IMutableList<T> and IMutableCollection<T> interfaces to provide the mutation methods that my new IList<T> and ICollection<T> interfaces lack.

I built a simple collection library around it, and you can download the source code and compiled binaries from BitBucket, or install the NuGet package:

M42.Collections – Specialized collections with more functionality, features and ease-of-use than the built-in .NET collection classes.


1) An array T[] in .Net 4.5 implements through its base class Array: ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable; and silently through the runtime: IList<T>, ICollection<T>, IEnumerable<T>, IReadOnlyList<T>, and IReadOnlyCollection<T>.

David Klempfner
  • 8,700
  • 20
  • 73
  • 153
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
  • 1
    And thank Odin, c# designers didn't went the java way with generics, i.e. =The Erasure=. With the erasure Array is the same as Array at runtime but it's such a terrible thing in all other aspects. – durilka Mar 25 '13 at 15:15
  • 1
    I can't use it at work since the software is proprietary (can't distribute the source). At least if it were LGPL or dual license with Apache2/MS-PL/MIT/BSD I could use it. – Dustin Kingen Mar 28 '13 at 22:54
  • @Romoku Actually it _is_ LGPL. If you found any text stating otherwise, I'd like to know so I can fix it. – Daniel A.A. Pelsmaeker Mar 28 '13 at 23:35
  • 2
    Well it looks like my eyes were deceiving me when I read the files because they are indeed LGPL. – Dustin Kingen Mar 28 '13 at 23:58
  • @KenKin Thanks for the bounty! I really appreciate it! – Daniel A.A. Pelsmaeker Apr 01 '13 at 18:54
  • 1
    @Virtlink: You deserve it. – Ken Kin Apr 01 '13 at 19:28
  • Code that receives a reference of type `Animal[]` and reads an element of that array into a variable of type `Animal` can know that even if the array isn't capable of accepting all instances of `Animal`, it's guaranteed to be capable of accepting the instance *that was read from it*. I know of no way of writing a generic IList-ish interface which could support that style of variance in a fashion that was remotely compatible with the way arrays handle it. – supercat Oct 22 '13 at 23:07
  • @supercat You're essentially saying: whatever object you get out of the array you can put back into it. While very true, it holds for any mutable list. I must be missing your point... – Daniel A.A. Pelsmaeker Oct 23 '13 at 01:19
  • @Virtlink: My point is that a `Zebra[]` may be cast to `Animal[]` and passed to code which expects to sort that latter type provided that every `Animal` it tries to store really is actually an instance of `Zebra`, but there's no elegant way to define a `ICollectionType` which can be cast to `ICollectionType` and have its indexed setter accept (at least at compile time) `Animal`. – supercat Oct 23 '13 at 15:26
  • @supercat The _get_ functionality of collections is _covariant_ (i.e. you can ask for `IArray` and give `IArray`, and _get_ an animal which is actually a zebra), but the _add/remove/set_ functionality is _contravariant_ (i.e. you can ask for `IArray` and give a `IArray`, and _add_ a zebra since its an animal). You can't have both: such types must be _invariant_. The fact that arrays are covariant and contravariant at the same time won't gain you anything here. – Daniel A.A. Pelsmaeker Oct 23 '13 at 20:18
  • @Virtlink: For an `ICollectionInterface` to accept a `T` as a method parameter would only violate the type safety of the underlying collection if it would be possible for the passed-in reference to be persisted without being validated as matching the underlying collection type. Because .NET knows that an attempt to write an `Animal` to a `Cat[]` can't persist to the array anything other than a `Cat`, it can allow the attempt. There's no way, however, for an interface method to promise that it won't persist a member without type-checking it first. – supercat Oct 23 '13 at 21:03
  • @Virtlink: If there were a way that a property setter or other method could have its parameter indicate to the outside world that it should only accept things that might be `T`, and internally regard the parameter as `Object` *except when T is a value type*, that would allow input-parameter covariance in the same way arrays do, but as far as I know no such facility exists. – supercat Oct 23 '13 at 21:10
15

[Update, new insights, it felt something was missing until now]

Regarding the earlier answer:

  • Arrays are covariant like other types can be. You can implement things like 'object[] foo = new string[5];' with covariance, so that is not the reason.
  • Compatibility is probably the reason for not reconsidering the design, but I argue this is also not the correct answer.

However, the other reason I can think of is because an array is the 'basic type' for a linear set of elements in memory. I've been thinking about using Array<T>, which is where you might also wonder why T is an Object and why this 'Object' even exists? In this scenario T[] is just what I consider another syntax for Array<T> which is covariant with Array. Since the types actually differ, I consider the two cases similar.

Note that both a basic Object and a basic Array are not requirements for an OO language. C++ is the perfect example for this. The caveat of not having a basic type for these basic constructs is not being able to work with arrays or objects using reflection. For objects you're used to making Foo things which makes an 'object' feel natural. In reality, not having an array base class makes it equally impossible to do Foo -- which is not as frequently used, but equally important for the paradigm.

Therefore, having C# without an Array base type, but with the riches of runtime types (particularly reflection) is IMO impossible.

So more into the details...

Where are arrays used and why are they arrays

Having a basic type for something as fundamental as an array is used for a lot of things and with good reason:

  • Simple arrays

Yea well, we already knew that people use T[], just like they use List<T>. Both implement a common set of interfaces, to be exact: IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection and IEnumerable.

You can easily create an Array if you know this. We also all know this to be true, and it's not exciting, so we're moving on...

  • Create collections.

If you dig into List you will end up with an Array eventually - to be exact: a T[] array.

So why's that? While you could have used a pointer structure (LinkedList), it's just not the same. Lists are continuous blocks of memory and get their speed by being a continuous block of memory. There's a lot of reasons about this, but simply put: processing continuous memory is the fastest way of processing memory - there are even instructions for that in your CPU that make it faster.

A careful reader might point at the fact that you don't need an array for this, but a continuous block of elements of type 'T' that IL understands and can process. In other words, you could get rid of the Array type here, as long as you make sure there's another type that can be used by IL to do the same thing.

Note that there's value and class types. In order to retain the best possible performance, you need to store them in your block as-such... but for marshalling it's simply a requirement.

  • Marshalling.

Marshalling uses basic types that all languages agree upon to communicate. These basic types are things like byte, int, float, pointer... and array. Most notably is the way arrays are used in C/C++, which is like this:

for (Foo *foo = beginArray; foo != endArray; ++foo) 
{
    // use *foo -> which is the element in the array of Foo
}

Basically this sets a pointer at the start of the array and increments the pointer (with sizeof(Foo) bytes) until it reaches the end of the array. The element is retrieved at *foo - which gets the element the pointer 'foo' is pointing at.

Note again that there are value types and reference types. You really don't want a MyArray that simply stores everything boxed as an object. Implementing MyArray just got a hell of a lot more tricky.

Some careful readers can point at the fact here that you don't really need an array here, which is true. You need a continuous block of elements with the type Foo - and if it's a value type, it must be stored in the block as the (byte representation of the) value type.

  • Multi-dimensional arrays

So more... What about multi-dimensionality? Apparently the rules aren't so black and white, because suddenly we don't have all the base classes anymore:

int[,] foo2 = new int[2, 3];
foreach (var type in foo2.GetType().GetInterfaces())
{
    Console.WriteLine("{0}", type.ToString());
}

Strong type just went out of the window, and you end up with collection types IList, ICollection and IEnumerable. Hey, how are we supposed to get the size then? When using the Array base class, we could have used this:

Array array = foo2;
Console.WriteLine("Length = {0},{1}", array.GetLength(0), array.GetLength(1));

... but if we look at the alternatives like IList, there's no equivalent. How are we going to solve this? Should introduce a IList<int, int> here? Surely this is wrong, because the basic type is just int. What about IMultiDimentionalList<int>? We can do that and fill it up with the methods that are currently in Array.

  • Arrays have a fixed size

Have you noticed that there are special calls for reallocating arrays? This has everything to do with memory management: arrays are so low-level, that they don't understand what growth or shrinking are. In C you would use 'malloc' and 'realloc' for this, and you really should implement your own 'malloc' and 'realloc' to understand why exactly having fixed sizes is important for all things you directly allocate.

If you look at it, there's only a couple of things that get allocated in a 'fixed' sizes: arrays, all basic value types, pointers and classes. Apparently we handle arrays differently, just like we handle basic types differently.

A side note about type safety

So why need these all these 'access point' interfaces in the first place?

The best practice in all cases is to provide users with a type safe point of access. This can illustrated by comparing code like this:

array.GetType().GetMethod("GetLength").Invoke(array, 0); // don't...

to code like this:

((Array)someArray).GetLength(0); // do!

Type safety enable you to be sloppy when programming. If used correctly, the compiler will find the error if you made one, instead of finding it out run-time. I cannot stress enough how important this is - after all, your code might not be called in a test case at all, while the compiler will always evaluate it!

Putting it all together

So... let's put it all together. We want:

  • A strongly typed block of data
  • That has its data stored continuously
  • IL support to make sure we can use the cool CPU instructions that make it bleeding fast
  • A common interface that exposes all the functionality
  • Type safety
  • Multi-dimensionality
  • We want value types to be stored as value types
  • And the same marshalling structure as any other language out there
  • And a fixed size because that makes memory allocation easier

That's quite a bit of low level requirements for any collection... it requires memory to be organized in a certain way as well as conversion to IL/CPU... I'd say there's a good reason it's considered a basic type.

atlaste
  • 30,418
  • 3
  • 57
  • 87
  • 1
    Added more to my answer... it felt like something was missing until now. I asked myself the question 'what limitations would a language having Array have' and came up with this. Concluding, I would definitely add an Array if I had to redesign the language from scratch. For this reason, I strongly feel that although the other answers here feel okay, they are both incorrect. – atlaste Jan 16 '13 at 08:10
  • @KenKin What kind of detailed information are you looking for? – atlaste Mar 25 '13 at 15:28
  • @KenKin Yea, and for the most part he's correct... but as I said, the solution is IMO also incorrect because of the 'basic array type' thing -- which is actually very visible and very, very useful. :-) I'll put down an example to illustrate. – atlaste Mar 25 '13 at 18:12
  • Oh, it's great if you would like to. – Ken Kin Mar 25 '13 at 18:25
  • 1
    Sure, there you go ;-) I guess I wrote a book about it instead of a simple example... :-) – atlaste Mar 25 '13 at 19:15
  • 1
    @StefandeBruijn What do you mean by 'the basic array type thing that is incorrect' in my post? Also, regarding your edit: to create a continuous block of _n_ elements of 'T' without arrays you'd need to create a class with _n_ fields of `T`. You can't do this at run-time in C# or the CLR. Also, multi-dimensional arrays can be created using nested vector arrays, and in current C# `T[][]` is even faster to use than `T[,]`. – Daniel A.A. Pelsmaeker Mar 25 '13 at 19:57
  • @Virtlink Well, one way or the other, you need a continuous block of elements. I'd say that's "basic" - you can call it a Buffer or a byte[], or whatever you like - but eventually you need it for various reasons. I'd say that's so "basic" (or "special" if you want) there's no point in not calling it a basic type or giving it special treatment. It's like wondering wether an 'int' should be called a 'byte[4]' - while you might get it working, and figure out all trouble, there's really no sense in doing it like that. I'd therefore say special syntax actually *helps* here. – atlaste Mar 25 '13 at 22:53
  • T[][] is also not the same as T[,], since the former is a pointer to a continuous block of memory whereas the latter is not. I'm not sure which of the two is faster, and it's probably also dependent on the case (cache lines etc).. either way it also doesn't matter, because the way it is represented in memory is so important. What I noticed is that you forgot to mention in 'history' that .NET was different by-design from Java in the fact that interop with COM and other languages was **one of the the most important design assets**. Arrays were (and are) one of the key elements to make that work. – atlaste Mar 25 '13 at 23:03
  • 1
    I agree there is a need for a continuous block of elements (an array), but it needs special runtime support and has special instructions for using it. Note that multidimensional arrays don't have special instructions so [mutlidimensional arrays will be slower](http://stackoverflow.com/q/597720/146622) than jagged arrays. However, there is _no_ need in C# 5 to give arrays also a special syntax. Object instantiation is well defined, but only for arrays it is different. That's historical from when generics didn't exist, but now it pollutes the C# language. Btw, `T[]` arrays _aren't_ type-safe. – Daniel A.A. Pelsmaeker Mar 25 '13 at 23:21
  • Sure they are. `int[] foo; foo[0] = "foo";` -> doesn't compile. I agree it's not like C++, but it definitely is there. But let's do a quick wrap-up, because this isn't really helping. We agree on that it was originally created because that seemed like the right thing at that time. We also agree having a collection with the properties of an array are a necessary for C#. That leaves us with the inheritance graph and syntax. You could introduce `Array : Array` in the language I suppose. Also, you could have `T[]` behave as `Array` which is the same with different syntax. – atlaste Mar 26 '13 at 08:30
  • If you had to redesign it from scratch, you could choose both, I agree on that - but we'd choose differently. So, the only question here remaining is how you would name your array. If it's `T[] : Array : Array`, I'd say that `Array` doesn't add anything here. There's a lot to say for the `T[]` syntax - it's the same in I don't know how many other languages and as I argue it's as close to a basic type as it gets - so why not give it special treatment. You call that polution, I'd call your solution a hack... *If we can agree on that, the conclusion is that it's just a matter of taste...* – atlaste Mar 26 '13 at 08:32
  • @Virtlink PS: I've just re-read my type safety example, and I agree that it's a very bad example... what I meant is that you need both a type for an array (T[]) and a base type (Array) - to be able use the compiler to your advantage. `T[] : Array : Array` and `T[] : Array` are both solutions that fit, just `int[]` without base type is not. (Oh and PS2: sorry about spamming the comments...) – atlaste Mar 26 '13 at 08:41
  • 2
    Yes we agree on all that. And I meant `T[] == Array : Array`, not `T[] : Array : Array`. And yes, using `Array` over `T[]` syntax is my _preference_, since everything else in C# works that way too. And today you work a lot less with arrays, as opposed to `IEnumerable` or `List`. `Array` would fit nicely in that list. What I meant with type-safety: `Animal[] animals = new Elephant[10]; animals[0] = new Giraffe();` shows arrays do not provide the compile-time type safety that other language constructs (including covariant generics) do. Run-time exception ahead! – Daniel A.A. Pelsmaeker Mar 26 '13 at 14:24
  • Well, we could simply introduce `IArray`... although the covariant behavior of arrays are strange, I give you that. I do a lot of low-level programming, so my *preference* is that arrays are named differently - to show it's part of the language with a very predictable set of properties and behavior. `Array` suggests someone is messing around with my block of memory, doing checks or whatever I don't want :-) I'd think twice before using a pointer on `Array` for the same reason - while I'd surely use it on a `T[]` any day of the week. Anyhow, we're in agreement. Thx for the discussion. – atlaste Mar 26 '13 at 14:56
12

Compatibility. Array is a historic type that goes back to the time that there were no generics.

Today it would make sense to have Array, then Array<T>, then the specific class ;)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
TomTom
  • 61,059
  • 10
  • 88
  • 148
  • I'll take the chance to ask, http://msdn.microsoft.com/en-us/library/system.array.aspx asserts "*thereby serving as the base class for all arrays in the common language runtime.*". By "serving as the base class", does the documentation mean "is the base class"? – e_ne Jan 14 '13 at 19:18
  • Like you are saying it won't be a problem except the reason of historic? – Ken Kin Jan 14 '13 at 19:49
  • 1
    No, it is the reason it is not generic. It is actually subclassed anyway - just not via normal genecics mechanisms. It is ancient. .NET 1.0. Hard to Change now. – TomTom Jan 14 '13 at 19:53
  • 1
    Yes. It is simply history. A LOT of the old codebase ignored generics, sadly. – TomTom Jan 14 '13 at 20:24
  • 1
    Yes and no. I would expect a lot of more tricky code to break. Reflection wise, where people make assumptions about the hierarchy. – TomTom Jan 14 '13 at 20:49
  • 1
    @Eve: The Framework contains a few "special" types whose descendants are regarded having certain traits *which the special types themselves lack*. For example, every type derived from `System.Enum` is a value type which has some integer type as its base representation, but `System.Enum` itself is a class type. Likewise derivatives of `System.ValueType` other than `System.Enum` are value types, but `System.ValueType` itself as a class type. A similar situation exists with `System.Array`. Every legitimate derivative type has an indexer, but `System.Array` does not. – supercat Mar 25 '13 at 21:43
5

Thus I'd like to know why it is not:

The reason is that generics were not present in the first version of C#.

But I cannot figure out what would be the problem myself.

The problem is that it would break a huge amount of code that uses the Array class. C# doesn't support multiple inheritance, so lines like this

Array ary = Array.Copy(.....);
int[] values = (int[])ary;

would be broken.

If MS were making C# and .NET all over again from scratch, then there probably would be no problem in making Array a generic class, but that is not the reality.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • 1
    I'm not sure I necessarily understand his answer. If reflection can't handle generic classes very well, then I would think making reflection more versatile would be the answer, not pigeonholing arrays into a non-generic class. But if you think his is a better answer to your question, accepting it is up to you. – JLRishe Jan 24 '13 at 08:05
3

In addition to the other issues people have mentioned, trying to add a generic Array<T> would pose a few other difficulties:

  • Even if today's covariance features had existed from the moment generics were introduced, they wouldn't have been sufficient for arrays. A routine which is designed to sort a Car[] will be able to sort a Buick[], even if it has to copy elements from the array into elements of type Car and then copy them back. The copying of the element from type Car back to a Buick[] isn't really type-safe, but it's useful. One could define a covariant array single-dimensional-array interface in such a way as to make sorting possible [e.g. by including a `Swap(int firstIndex, int secondIndex) method], but it would be difficult to make something that's as flexible as arrays are.

  • While an Array<T> type might work well for a T[], there would be no means within the generic type system to define a family that would include T[], T[,], T[,,], T[,,,], etc. for an arbitrary number of subscripts.

  • There is no means in .net to express the notion that two types should be considered identical, such that a variable of type T1 can be copied to one of type T2, and vice versa, with both variables holding references to the same object. Someone using an Array<T> type would probably want to be able to pass instances to code which expects T[], and accept instances from code which uses T[]. If old-style arrays couldn't be passed to and from code that uses the new style, then the new-style arrays would be more of an obstacle than a feature.

There might be ways of jinxing the type system to allow for a type Array<T> that behaved as it should, but such a type would behave in many ways that were totally different from other generic types, and since there is already a type which implements the desired behavior (i.e. T[]), it's not clear what benefits would accrue from defining another.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • @KenKin: There is no way to define a true array of arrays within the .net type system; attempting to do so will instead define an array of *references* to arrays. If generic types could be defined using integers as well as `Type` parameters, and if there were an array value type `ValueArray`, then an `Int[3,5]` might be representable in the type system as an `Array>` of size 3, but no such facilities exist in the Framework. C# can implement fixed arrays on "bare metal", but the Framework doesn't understand them and can't validate their usage. – supercat Mar 25 '13 at 20:44
  • 1
    @KenKin: A C# `Foo[][]` is equivalent to a C `*Foo[]`, while a C# `Bar[,]` is equivalent to a C `Bar[][]`. – supercat Mar 25 '13 at 21:26
2

As everyone says - original Array is non-generic because there was no generics when it came into existence in v1. Speculation below...

To make "Array" generic (which would make sense now) you can either

  1. keep existing Array and add generic version. This is nice, but most usages of "Array" involve growing it over time and it most likely reason that better implementation of the same concept List<T> was implemented instead. At this point adding generic version of "sequential list of elements that does not grow" does not look very appealing.

  2. remove non-generic Array and replace with generic Array<T> implementation with the same interface. Now you have to make compiled code for older versions to work with new type instead of existing Array type. While it would be possible (also most likely hard) for framework code to support such migration, there is always a lot of code that written by other people.

    As Array is very basic type pretty much every piece of existing code (which includes custom code with reflection and marshalling to with native code and COM) uses it. As result price of even tiny incompatibility between versions (1.x -> 2.x of .Net Framework) would be very high.

So as result Array type is there to stay forever. We now have List<T> as generic equivalent to be used.

Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • You seem to assume that `T[]` and `Array` are equivalent, which they are not. And `List` is _not_ the generic equivalent of an array `T[]` or any array `Array`. – Daniel A.A. Pelsmaeker Mar 27 '13 at 16:17
  • @Virtlink, On List vs.Array - Why do you consider `List` not "an array concept"? From my point of view both `T[]` (implemented via `Array`) and `List` both provide ordered sequence of elements with guarantee of O(1) indexing. List is more flexible. Yes List is not usable in all cases where `T[]` can be used (like PInvoke) but it not something I care about. – Alexei Levenkov Mar 27 '13 at 16:29
  • @Virtlink on `Array` vs `T[]`: `Array` is essentially generic implementation (in sense "uses exactly the same concept used late for generics") for `T[]` - so yes, to extent I treat them equivalent. My understanding of original question is "why there is syntactic sugar (`T[]`) on top of non-generic class when true generic `Array` could be used to have more direct mapping" - could be wrong/unrelated... – Alexei Levenkov Mar 27 '13 at 16:37
  • `T[]` is a generic array with covariant type parameter `T` (although different from generics as they are used in today's C#), but `Array` is not relevant here. It would have worked just as well if all arrays `T[]` inherited from `Object` directly. They are not equivalent. And arrays are a basic concept that provides you with a contiguous area of memory. The fact that this results in O(1) indexing and that a list _also happens_ to have O(1) indexing does not make them equivalent, and certainly doesn't make a list equivalent to fixed-size arrays. – Daniel A.A. Pelsmaeker Mar 27 '13 at 16:45
  • @Virtlink Good points - our definitions are very different, and you probably should write your own answer which very well could be much more interesting/useful. My understanding of `Array`'s sole purpose to be implementation of `T[]` and I see how it is different from your "`Array` happen to be used for implementation of `T[]`". And for "arrays as basic concept" is different to - "indexable sequence of elements" vs. "contiguous memory block of fixed size"... – Alexei Levenkov Mar 27 '13 at 17:01
  • Thank you, I'll write my own answer here. – Daniel A.A. Pelsmaeker Mar 27 '13 at 18:01
1

Maybe I'm missing something but unless the array instance is casted to or used as an ICollection, IEnumerable, etc.. then you don't gain anything with an array of T.

Arrays are fast and are already type safe and don't incur any boxing/unboxing overhead.

user1758003
  • 162
  • 1
  • 7