Is async streams and "await foreach" just syntactic sugar for things that could still be done beforehand, albeit with more code?
I mean, "slightly more code" - you can say the same about "async ... await" in general, but then it's "insanely more code" and huge potential for error.
Not quite. F# and a few other languages have had this capability for a few years now. Its effectively making the MoveNext() function on the enumerator object an async operation instead of a blocking one. This allows a foreach loop for example to wait until the next object comes without tying up the thread. Useful for things like pulling off a remote message queue, messages off a network, etc where the waiting for the next item in your "foreach" loop involves async/task based operations and you don't want to block the current thread.
nope async streams weren't really possible before without an unnecessary allocation.
i.e. a List<Task<T>> is not the same as an AsyncEnumerable<T>. List<Func<Task<T>>> would be more equivalent, but you needed to have a List<T> which you need to fill beforehand.
I mean, "slightly more code" - you can say the same about "async ... await" in general, but then it's "insanely more code" and huge potential for error.
It looks that way to me.