Technically there are two types of arrays. Vector types or Matrix types. The runtime refers to Vector types as Sz_Array
and they are the type you get when you declare a 1d array*. I have no clue why. Matrix types represent multidimensional arrays. Unfortunately they both inherit from Array
and no other intermediary types.
Why doesn't Array class expose its indexer directly?
The reason why you can only access the indexer for 1d arrays when you have it as a T[]
is because the indexer for 1d arrays is implemented in the runtime through IL opcodes.
e.g.
static T GetFirst<T>(T[] a)
{
return a[0]:
}
Translates to the following il::
L_0000: ldarg.0
L_0001: ldc.i4.0
L_0002: ldelem.any !!T
L_0007: ret
Where as the following C#
private static T GetFirst<T>(IList<T> a)
{
return a[0];
}
translates to this IL.
L_0000: ldarg.0
L_0001: ldc.i4.0
L_0002: callvirt instance !0 [mscorlib]System.Collections.Generic.IList`1<!!T>::get_Item(int32)
L_0007: ret
So we can see that one is using an opcode ldelem.any
, and the other is callvirt
a method.
The runtime injects IList<T>,IEnumerable<T>
at runtime for arrays. The logic for them in the MSIL is located in the class SZArrayHelper
The runtime which provides the implementation, also creates two helper methods for every array generated to help languages that do not support indexers (if such a language exists) C# does not expose them but they are valid methods to call.
They are:
T Get(Int32)
void Set(Int32, T)
These methods are also generated for matrix types arrays depending on the dimension and are used by C# when you call indexers.
However, unless you actually specify that your is a typed array you don't get the indexer. As Array does not have an indexer method. The opcodes can't be used because at compile time you need to know that the array in question is a vector type and the element type of the array.
But Array implements IList! That has an indexer can't I call it?
Yes it does, but the implementation for the IList methods are explicit implementations so they can only be called in C# when cast or when bound by a generic constraint. Probably, because for any non vector type of array it throws a not supported exception when you call any of its methods. Since its only conditionally supported the creators of the run-time probably want you to cast it for times when you know for a fact this is a 1d array, but I can't name the type right now.
Why does array implement IList
if for any multidimensional arrays the implementation throws not supported?
This is probably a mistake that was there since 1.0. They can't fix it now as someone for whatever reason might be casting a multidimensional array to an IList
. In 2.0 they added some runtime magic to add implementation to the vector type classes at runtime so that only 1d arrays implement IList<T>
and IEnumerable<T>
.
And we can interpolate your next question::
How can I get the indexers to show up without casting?
Change the method signature to something like::
public static T[] ToArray<T>(this T source)
But you might say your ToArray does not return a T[], It returns something else what do I do? If you can specify the return type explicitly just do that.
If its a reference kind you can always abuse array covariance and change the return type to object[]
but then you are at the mercy of ArrayTypeMismatchException. This won't work if you are getting a valuetype back as that cast is illegal. At this point you can just return IList
but then you are boxing the elements and you are still at the mercy of ArrayTypeMismatchException.
Array is the base class for all array types for a reason it has helper methods to help you access the content like GetValue
and SetValue
and you'll note they have overloads that take arrays of indices so that you can access elements in Nd as well as 1d arrays.
e.g.
IList myArray = ToArray(myValues);
// myArray is actually a string array.
myArray[0]='a';
// blows up here as you can't explicitly cast a char to a string.
So the short of it is you don't know the explicit type. And every inheritor of Array
implementing IList
, even when it doesn't make much sense, is an implementation detail that they can't change since it was there since 1.0.
- Technically this isn't a 100% true. You can create a matrix type array that only has 1 dimension. This eldritch abomination can be created in IL or you can create the type using the
typeof(int).MakeArrayType(1)
Notice how you now have a System.Int32[*]
instead of System.Int32[]
Array
like this, instead ofchar[]
? – Kilocycleobject[]
to gain use of the indexer. – Pubischar[]
toobject[]
, covariant array conversions only for work arrays of reference types. – Kilocycleobject[]
or genericT[]
work for you? – KilocycleIEnumerable<T>
or how exactly would you useT[]
. Maybe you could ask another question about that. – KilocycleT
and you aren't able to figure it out statically fromT
, then the user will have to specify it:TResult[] ToArray<TSource, TResult>(this TSource source)
. – KilocycleArray
already does have that indexer, except it's an explicit interface implementation, so it can't be used directly. – KilocycleT[] ToArray<T>(this T source)
. – HeighhoIList<T>
, while an n-dimensional array is not. Having an indexer at Array gives all n-dimensional arrays the 1-dimensional indexer, which you can argue to be a bad design choice. The other way around, you can also argue that Array should have been designed 1-dimensional and MultiDimensionalArray (give it a name...) as n-dimensional; apparently the C# team decided otherwise. I can imagine that this idea originated from Pascal. – Briefcase