Only the designers of the .NET runtime can tell you their actual reasoning. But my guess is that memory safety is paramount in .NET, and it would be very expensive to maintain both memory safety and mutable array lengths, not to mention how much complicated any code with arrays would be.
Consider the simple case:
var fun = 0; for (var i = 0; i < array.Length; i++) { fun ^= array[i]; }
To maintain memory safety, every array access must be bounds-checked, while ensuring that the bounds checking isn't broken by other threads (the .NET runtime has much stricter guarantees than, say, the C compiler).
So you need a thread-safe operation that reads data from the array, while checking the bounds at the same time. There's no such instruction on the CPU, so your only option is a synchronization primitive of some kind. Your code turns into:
var fun = 0; for (var i = 0; i < array.Length; i++) { lock (array) { if (i >= array.Length) throw new IndexOutOfBoundsException(...); fun ^= array[i]; } }
Needless to say, this is hideously expensive. Making the array length immutable gives you two massive performance gains:
- Since the length cannot change, the bounds checking doesn't need to be synchronized. This makes each individual bounds check vastly cheaper.
- ... and you can omit the bounds checking if you can prove the safety of doing so.
In reality, what the runtime actually does ends up being something more like this:
var fun = 0; var len = array.Length; // Provably safe for (var i = 0; i < len; i++) { // Provably safe, no bounds checking needed fun ^= array[i]; }
You end up having a tight loop, no different from what you'd have in C - but at the same time, it's entirely safe.
Now, let's see the pros and cons of adding array shrinking the way you want it:
Pros:
- In the very rare scenario where you'd want to make an array smaller, this would mean the array doesn't need to be copied over to change its length. However, it would still require a heap compaction in the future, which involves a lot of copying.
- If you store object references in the array, you might get some benefits from cache locality if the allocation of the array and the items happens to be colocated. Needless to say, this is even rarer than Pro #1.
Cons:
- Any array access would become hideously expensive, even in tight loops. So everyone would use
unsafe code instead, and there goes your memory safety. - Every single piece of code dealing with arrays would have to expect that the length of the array can change at any time. Every single array access would need a
try ... catch (IndexOutOfRangeException), and everyone iterating over an array would need to be able to deal with the changing size - ever wondered why you can't add or remove items from List<T> you're iterating over? - A huge amount of work for the CLR team that couldn't be used on another, more important feature.
There's some implementation details that make this even less of a benefit. Most importantly, .NET heap has nothing to do with malloc/free patterns. If we exclude the LOH, the current MS.NET heap behaves in a completely different way:
- Allocations are always from the top, like in a stack. This makes allocations almost as cheap as stack allocation, unlike
malloc. - Due to the allocation pattern, to actually "free" memory, you must compact the heap after doing a collection. This will move objects so that the free spaces in the heap are filled, which makes the "top" of the heap lower, which allows you to allocate more objects in the heap, or just free the memory for use by other applications on the system.
- To help maintain cache locality (on the assumption that objects that are commonly used together are also allocated close to one another, which is quite a good assumption), this may involve moving every single object in the heap that's above the freed space down. So you might have saved yourself a copy of a 100-byte array, but then you have to move 100 MiB of other objects anyway.
Additionally, as Hans explained very well in his answer, just because the array is smaller doesn't necessarily mean that there's enough space for a smaller array in the same amount of memory, due to the object headers (remember how .NET is designed for memory safety? Knowing the right type of an object is a must for the runtime). But what he doesn't point out is that even if you do have enough memory, you still need to move the array. Consider a simple array:
ObjectHeader,1,2,3,4,5
Now, we remove the last two items:
OldObjectHeader;NewObjectHeader,1,2,3
Oops. We need the old object header to keep the free-space list, otherwise we couldn't compact the heap properly. Now, it could be done that the old object header would be moved beyond the array to avoid the copy, but that's yet another complication. This is turning out to be quite an expensive feature for something that noöne will ever use, really.
And that's all still in the managed world. But .NET is designed to allow you to drop down to unsafe code if necessary - for example, when interoperating with unmanaged code. Now, when you want to pass data to a native application, you have two options - either you pin the managed handle, to prevent it from being collected and moved, or you copy the data. If you're doing a short, synchronous call, pinning is very cheap (though more dangerous - the native code doesn't have any safety guarantees). The same goes for e.g. manipulating data in a tight loop, like in image processing - copying the data is clearly not an option. If you allowed Array.Resize to change the existing array, this would break entirely - so Array.Resize would need to check if there's a handle associated with the array you're trying to resize, and throw an exception if that happens.
More complications, much harder to reason about (you're going to have tons of fun with tracking the bug that only occurs once in a while when it so happens that the Array.Resize tries to resize an array that just so happens to right now be pinned in memory).
As others have explained, native code isn't much better. While you don't need to maintain the same safety guarantees (which I wouldn't really take as a benefit, but oh well), there's still complications related to the way you allocate and manage memory. Called realloc to make a 10-item array 5-item? Well, it's either going to be copied, or it's still going to be the size of a 10-item array, because there's no way to reclaim the left-over memory in any reasonable manner.
So, to make a quick summary: you're asking for a very expensive feature, that would be of very limited benefit (if any) in an extremely rare scenario, and for which there exists a simple workaround (making your own array class). I don't see that passing the bar for "Sure, let's implement this feature!" :)
mallocandrealloc, sometimesreallocmoves objects around, sometimes it doesn't. One of the reasons it sometimes does is that the memory manager has a separate pool for small memory blocks. Reducing a large memory block to a small one may then require it being moved to the separate pool. But I couldn't say if it's the same for .NET.