Ok, I've finally tracked down the corruption problem with the Vertex Array example. It was obvious after all.
You see, when you create a Vertex Array and call GL.VertexPointer (and the rest of the family), you are supposed to keep the array data for as long as you continue to use this Vertex Array. Which is something you have to request explicitly in .Net.
The .Net Garbage Collector, optimizes the heap layout aggressively by moving memory around to plug holes created by collected objects. It can do this because it knows the exact layout of every object in the managed heap. This even works when you pass a managed object to an unmanaged function: the default .Net P/Invoke routines, as well as the data marshalling performed by OpenTK ensures that the object stays put for the duration of the call.
And this is where the Vertex Array example fails. Your call GL.VertexPointer internally stores a pointer to the the vertex data. By the time you call GL.DrawElements or GL.DrawArrays (which dereference this pointer), the Garbage Collector may have moved the vertex data in memory, rendering the pointer invalid!
How do you stop this from happening? There are two solutions: either pin the vertex data in memory using a System.Runtime.InteropServices.GCHandle object (of type GCHandleType.Pinned), or use Vertex Buffer Objects (VBO's) instead of Vertex Arrays. My advice is to follow the second route, as GCHandles can seriously undermine the efficiency of the Garbage Collector (and you don't want that!). With VBO's you upload the memory once (during creation) and you are free to discard the vertex data afterwards - exactly what we want! You'll get significantly higher performance, too.
The only problem with VBO's is that they may not be supported on older hardware (pre- OpenGL 1.4).