If you mean what I think you mean by "manually currying", it's likely because behind the scenes, there is a proxy class created for most (all?) lambdas that contains everything it needs to execute, including any scope variables maintained. So there's some extra overhead there, though not as much as you'd expect since everything, even value types, are pulled in by reference in the context of lambdas.
I did not know that! That explains a lot of the memory performance problems I was having (structs being moved to the heap...icky); lambdas are sneaky little turds.
I solved the problem by avoiding lambdas altogether in code where performance is a concern, using delegates instead and ensuring all arguments are passed in rather than closed on (so structs would remain structs, GC pressure is avoided).
Imagine if the CLR allowed optional borrowing annotations ala Rust. Then safe non leaking functions would no longer need their callers to use the heap. Even APIs like String.Split could be actually efficient. In fact you might even get away with zero alloc for it, for some small cases.
Lambdas kind of mess that up, since they are indirect and dynamic by their nature and basically require GC to work. Heck, GC was originally invented to support Lisp.
There was, maybe is, some work being done on systems C#, not sure about the status though.
I don't see how that is any different, and in fact, lambdas would be one of the great things to benefit from stack allocation.
You just have to annotate or analyze the functions that don't takeover/leak a reference to something.
For something like List.ConvertAll (aka map), ConvertAll does not leak the lambda, it's pure in both input and output. So a stack allocated lambda works just fine. So the type signature for ConvertAll would indicate that it's Pure for parameters passed in. With that info, the compiler is free to pass lambdas on the stack to it.
So whenever the lambda is going to be created, the C# compiler just needs to look at the usage of it - is it passed only to Pure functions? If so, then no need to allocate a whole new object on the heap.
They could make it even cooler by doing Haskell's "fusion" which also contributes to deforesting. We just need the CLR (or even the C# and F# compilers) to provide real inlining. Then when you pass a lambda to an inlined function, you can avoid creating the closure in the first place! For common patterns like "xs.Select(...).Where(...).Aggregate(...)", if each function was inlined and pure, the resulting code could literally be equivalent to a big loop, as if you had hand-written the thing. No allocation all over the place, and much better resulting JIT'd code. I've wanted this in F# for a while (and F# does have inlining so you do get better code than C#), but Rust actually implements it, so lambdas are cost-free in many cases. That is progress.