Hard to compare the two, they have very little in common. C++ inherited this array syntax and behavior from the C language. Which was designed with only one goal in mind, only as-fast-as-possible was ever considered. The word "allocate" you use is already a poor mismatch with what happens at runtime, the int[3] syntax merely reserves space. Exactly where that happens depends on the location of the declaration, inside a function it will reserve space on the stack frame, outside it will reserve space in the data section.
With no execution guarantees, the elements are not initialized, the runtime doesn't keep track of the size of the array, there is no index checking. As fast as possible, features that otherwise launched billions of bugs and tens of thousands of malware attacks. A buffer overflow is a standard problem in a C or C++ program, producing very hard to diagnose bugs when it corrupts memory, randomly overwriting other variables or the return address of a function. Or commandeer a program simply with data, the malware attack vector.
Not the C# way of course. Array types are reference types in the .NET Framework, their storage is always allocated from the GC heap. The fast gen#0 heap if the array is small (less than 85KB), from the Large Object Heap otherwise. There is lots and lots of glue in the jitter and the CLR to make them still as fast as possible, in spite of their guarantees:
- there is dedicated IL opcode to create an array, Opcodes.NewArr, the jitter translates it to a direct call into the CLR.
- this helper function dynamically creates the array type if it doesn't exist yet (compare to Type.MakeArrayType()), allocates the storage from the GC heap and initializes the object, setting the Length property and initializing the array members so they all have their default(T) value.
- accessing the elements of the array in your code again produces dedicated IL opcodes, like Opcodes.LdElem. The jitter translates them into machine code instructions that check the array bounds and accesses the array element.
- other array members produce direct calls to the internal SZArrayHelper class, optimized to produce fast-as-possible code for one-dimensional arrays.
- the code optimizer in the jitter looks for opportunities to eliminate the array bound checks, possible when it can see from the code that the index expression cannot be out-of-bounds. This is often possible, particularly when you use foreach or write a for(;;) loop that uses the array's Length property, array access is then just as fast as the equivalent in C.
- the garbage collector has built-in knowledge of arrays, it rearranges array elements of a reference type when it compacts the heap so they can be sequentially accessed in code that iterates the array. Very, very important on modern processors that depend on the CPU caches to make memory access fast. This is where managed code can beat native C or C++ code handily, native memory allocations are stuck wherever they happen to exist.
C# still has syntax to emulate C arrays, without the safety guarantees. You can use them when you found that the array code is in the critical path for your program. These constructs require you to use the unsafe keyword, they have the same problems as the equivalent C code:
- the stackalloc keyword is available to allocate an array from the stack frame instead of the GC heap. Just like a local array variable does in a C program. It is a fallback when the local arrays in your C# code produce too much garbage and GC collections start to dominate your program's execution time.
- the fixed keyword is available to declare arrays that can be accessed without bounds checking. The alternative for stackalloc for arrays that need to survive the method call. It is a fallback when you can't get the index checking eliminated and the profiler tells you that it becomes critical. This is generally only the case for short arrays, the kind where the element access isn't the dominant cost.
Addressing your questions with the above info in mind:
Which class arr is an object of?
As you can tell from the 2nd bullet, an array has a dedicated type and it is created on the fly without you explicitly declaring it in your program. The CLR otherwise maintains the illusion that it is a type strongly correlated to the element type, like the C# language syntax does, the name of the array type for an array of ints is "System.Int32[]". It is however a distinct type in the CLR, a class that logically derives from System.Array and practically derives from System.SZArrayHelper. My favorite phrase for such type shenanigans is "quacks like a duck-typing".
Why the designers of c# go with the above syntax?
It is syntax sugar, pretty effective at being short and understandable. It might have looked different if generics would have been available at .NET 1.0, I kind of doubt it. Notable as well is that C# has a bit too much sugar for multi-dimensional arrays (like int[,]
) and not enough for jagged arrays (like int[][]
), they can lull a programmer into writing poorly performing code. They are expensive, index expressions require multiple multiplications and bound checks and it is way too easy to access the array elements in an order that is very unfriendly to the CPU caches.