You've never actually verified this claim, have you? Next time - please do it.
- structs, with auto, sequential and explicit layout, with complex SROA and promotion handling
- stack-allocated buffers, unsafe fixed and inline arrays, even managed objects can have their layout configured
- monomorphized struct generics compiled the same way they do in Rust
- raw and byref pointers with pointer arithmetic
- portable and platform-specific SIMD, platform-specific intrinsics
- zero-cost interop which enables calls into malloc/free at the cost of C/C++ (you do not actually need this - there is a managed reimplementation of mimalloc which is fully competitive with the original)
- static linking with native dependencies when using nativeaot
In C, I can just write the whole program without calling malloc. I write my own allocator tuned for my programs behavior, I even have multiple allocators if needed. I just have a lot more control available. If you can't do this in a language, you can't claim its performance is close to C. Performance is about control.
You keep ignoring the point. Show me how can I write a program in C# that works with a fixed size memory region, lets say 512KB. It should not do any dynamic memory allocation.
// Please, just allocate a normal array instead, there is no benefit to this if it lives throughout the whole application lifetime
var bytes = (stackalloc byte[512 * 1024]);
// or, but, please, at least use a span instead of C nonsense of tracking offsets by hand
// it's not 1980 anymore
var ptr = stackalloc byte[512 * 1024]; // byte*
// or
[InlineArray(256 * 1024)]
struct CommandList { byte _; }
var commands = new CommandList();
// or
unsafe struct State
{
public fixed byte CommandList[256 * 1024];
}
Whichever you like the most, they do have somewhat different implications - stackalloc is C alloca while struct-based definitions have the exact same meaning as in C.
To avoid running into stack space limitations, you can place a fixed buffer or any of these large arrays into a static field. It gets a fixed static offset in the memory.
C# goes much further and also provides zero-cost abstractions via struct generics with interface constraints. Or without - you can also apply pattern matching against a generic T struct and it will be zero-cost and evaluated at compilation (be it with RyuJIT or ILC (IL AOT Compiler)).
Not really. You don't have the same level of control over memory in managed languages