可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
int[] a = new int[5];
string[] b = new string[1];
The types of both a
and b
inherit from the abstract System.Array
, but there is no real classes in the built-in library(it seems that there are some runtime types, you can't find the type defination class of an int[]
). Can you tell me what happens while compiling? And why did they(the c# team) make this design(I mean why it's not something like Array<T>
,instead they are using an abstract class with compiler magics)?
回答1:
Trying to reason this out within the .NET type system doesn't get you very far. There is core support built into the JIT compiler and the CLR to deal with creating arrays. A statement like this:
var arr = new int[5];
Generates this IL:
IL_0001: ldc.i4.5
IL_0002: newarr [mscorlib]System.Int32
Which the JIT compiler then translate into this machine code:
00000035 mov edx,5 ; arg2 = array size
0000003a mov ecx,6F535F06h ; arg1 = typeof(int)
0000003f call FFD52128 ; call JIT_NewArr1(type, size)
Core ingredients here are the dedicated IL opcode, newarr, instead of the usual newobj opcode that creates an instance of a class. And the simple translation to a CLR helper function that actually gets the object created. You can have a look-see at this helper function with the SSCLI20 source code, clr\src\vm\jithelpers.cpp
. Too large to post here, but it is heavily optimized to make this kind of code run as fast possible, having direct access to the type internals available to CLR code.
There are two of these helpers available, JIT_NewArr1() creates one-dimensional (vector) arrays and JIT_NewMDArr() creates multi-dimensional arrays. Compare to the two overloads available for Type.MakeArrayType().
回答2:
And why did they(the c# team) make
this design(I mean why it's not
something like Array...
Generics are ideal for defining a container, as they constrain the element type so you can't insert type A and try to retrieve type B.
But generics were not added until CLR2/C#2. So arrays had to provide type safety in their own way.
Even so, it's not that different to generics. You note that there is no special class for int[]
. But nor would there be for Array<int>
. In generics there would only be the generic class Array<T>
, and the CLR "magically" creates specialised versions for distinct type argument you use. So it would be no less "magic" if generics were used.
Despite this, in the CLR the type of any object is reified (it exists as a value you can manipulate), of type Type
, and can be obtained with typeof
. So although there is no code declaration of any array type (and why would you need to see it?) there is a Type
object that you can query.
By the way, there was a design flaw in the way arrays constrain element types. You can declare an array:
int[] ints = ...
You can then store it in a looser variable:
object[] objs = ints;
But that means you can insert a string (at least it appears so at compile time):
objs[3] = "Oh dear";
At runtime it throws an exception. The idea of static type checking is to catch this kind of thing at compile time, not runtime. Generics would not have had this problem because they don't give assignment compatibility to generic class instances based on the compatibility of their type parameters. (Since C#4/CLR4 they have gained the ability to do that where it makes sense, but that wouldn't make sense for a mutable array.)
回答3:
Look at the Array
class.
When declaring an array using the []
syntax, the compiler, behind the scenes will use this class for you.
For C#, []
becomes a type that inherits from System.Array
.
From the C# 4.0 spec:
§12.1.1 The System.Array type
The type System.Array is the abstract base type of all array types. An implicit reference conversion (§6.1.6) exists from any array type to System.Array, and an explicit reference conversion (§6.2.4) exists from System.Array to any array type. Note that System.Array is not itself an array-type. Rather, it is a class-type from which all array-types are derived.
回答4:
There is such class. You cannot inherit it, but when you write "int[]" the compiler creates a type that inherits System.Array. So if you declare a variable:
int[] x;
This variable will have a type that inherits System.Array, and therefore has all its methods and properties.
This is also similar to delegates. When you define a delegate:
delegate void Foo(int x);
delegate int Bar(double x);
Then the type Foo
is actually a class that inherits System.MulticastDelegate
and Bar
is a class that inherits System.Delegate
.
回答5:
I would recommend getting the ECMA 335 spec and looking for Arrays if you want to know the low level detail: http://www.ecma-international.org/publications/standards/Ecma-335.htm
回答6:
I went digging through the ECMA 335 spec, so I figured I'd share what I read.
Exact array types are created automatically by the VES when they are required. Hence, the
operations on an array type are defined by the CTS. These generally are: allocating the array
based on size and lower-bound information, indexing the array to read and write a value,
computing the address of an element of the array (a managed pointer), and querying for the rank,
bounds, and the total number of values stored in the array.
The VES creates one array type for each
distinguishable array type.
Vectors are subtypes of System.Array, an abstract class pre-defined by the CLI. It provides several
methods that can be applied to all vectors. See Partition IV.
While vectors (§II.14.1) have direct support through CIL instructions, all other arrays are supported by
the VES by creating subtypes of the abstract class System.Array (see Partition IV)
While vectors (§II.14.1) have direct support through CIL instructions, all other arrays are supported by
the VES by creating subtypes of the abstract class System.Array (see Partition IV)
The class that the VES creates for arrays contains several methods whose implementation is supplied
by the VES:
It goes on to state, quite verbosely, that the methods supplied are:
- Two constructors
- Get
- Set
- Address (returns a managed pointer)
VES means Virtual Execution System, and the CLR is an implementation of it.
The spec also details how to store the data of the array (contiguously in row-major order), what indexing is allowed in arrays (0-based only), when a vector is created (single dimensional, 0-based arrays) as opposed to a different array type, when the CIL instruction newarr
is used as opposed to newobj
(creating a 0-based, single dimensional array).
Basically everything that the compiler has to do to build the method lookup tables etc.. for a regular type, it has to do for arrays, but they just programmed a more versatile and slightly special behavior into the compiler / JIT.
Why did they do it? Probably because arrays are special, widely used, and can be stored in an optimized fashion. The C# team did not necessarily make this decision though. It's more of a .NET thing, which is a cousin to Mono and Portable.NET, all of which are a CIL thing.
回答7:
Arrays are special to CLR. They're allocated with 'newarr' instruction, and elements are accessed with 'ldelem*' and 'stelem*' instructions, not via System.Array methods;
see http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.newarr.aspx
You can check out ildasm output to see how arrays are represented.
So, to answer your question - no new type declaration is generated for any particular array.
回答8:
[] is a syntax(syntatic sugar) for defining Arrays in c#. Maybe CreateInstance will be replaced at runtime
Array a = Array.CreateInstance(typeof(int), 5);
is same as
int[] a = new int[5];
Source for CreateInstance (taken from reflector)
public static unsafe Array CreateInstance(Type elementType, int length)
{
if (elementType == null)
{
throw new ArgumentNullException("elementType");
}
RuntimeType underlyingSystemType = elementType.UnderlyingSystemType as RuntimeType;
if (underlyingSystemType == null)
{
throw new ArgumentException(Environment.GetResourceString("Arg_MustBeType"), "elementType");
}
if (length < 0)
{
throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
}
return InternalCreate((void*) underlyingSystemType.TypeHandle.Value, 1, &length, null);
}