Si en aquel entonces C# sólo funcionaba en Windows, ¿por qué tenía una máquina virtual?

Así que casi todas las respuestas aquí tratan de la futura portabilidad del código, lo cual no tiene nada que ver. La razón por la que se ejecutaba en una VM es, en gran medida, que hay beneficios al ejecutarse en una VM¡!

Al principio de .NET había C#/VB.NET/C++(cli)/etc. Escribir código para poder interactuar entre ellos no sería tan difícil como producir todo el CLR y las variadas especificaciones que lo acompañaban. ¿Por qué formalizar IL si nuestro único objetivo es permitir las llamadas entre lenguajes, etc.?

El punto de tener todo compilado en el mismo lenguaje intermedio (vale, el modo mixto C++ es una excepción) era un poco diferente. Hay una razón realmente obvia por la que las cosas fueron en esta dirección. Si se mira históricamente, en la época que se acerca al año 2000, había serias dudas sobre si x86 iba a ser "la arquitectura" en el futuro (había bastantes estándares que competían entre sí, ¡incluso Windows soportaba varios!) y un gran punto de dificultad era conseguir que el código funcionara en múltiples entornos (obviamente, el jvm también tenía este objetivo). Sin embargo, había otros problemas. Uno realmente grande y evidente era la memoria, miremos los tamaños de memoria aproximados en las máquinas...

'82 a '84 - 1KB a 16KB
'85 a '89 - 512KB a 640KB
'89 a '92 - 1MB a 2MB
'93 a '94 - 4MB a 8MB
'95 a '99 - 32MB a 128MB
'00 a '01 - 256MB a 512MB
'02 a '04 - 1GB a 2GB
'05 a '09 - 3GB a 4GB
'10 a hoy - 6GB a 48GB

Si estuvieras sentado a mediados->finales de los 90 ¿cuál sería uno de tus principales pensamientos?! 32 bits vs 64 bits seguro que parece un gran problema no 😉 ¡Ten en cuenta que de 256mb->2gb pasaron +-4 años! Es probable que una empresa como microsoft tenga grandes problemas de servicio al cliente de "hola, he descargado este software y no se puede ejecutar en mi máquina".

Hubo múltiples soluciones en el espacio de 32/64. Algunas eran nuevas arquitecturas de procesador otras como la de intel (¡bueno una de intels!) eran instrucciones añadidas. Incluso si hubiera existido un mejor hardware (y Microsoft se deshiciera de los otros procesadores que soportaba) seguía existiendo el problema del uso generalizado de dos sistemas diferentes. Ahora bien, para un sistema operativo esto no es un problema importante. ¿Qué pasa con tu aplicación que toma una foto y le pone un sombrero de bruja? Tendrías que compilar como mínimo para dos objetivos (con otras arquitecturas IA64 (ISA), etc., ¿cuántas compilaciones necesitas?) Ahora imagina que todos los equipos que escriben una aplicación en casa/producto/etc. tuvieran que hacer esto. También tendrías que probar tu producto en las diferentes arquitecturas, etc., ¿no? ¿Empiezas a ver el problema? El mayor problema aquí, además de los múltiples... es la incertidumbre. ¿Cuál sería el estándar dentro de 5 años? ¿Qué pasa si un nuevo procesador no está disponible? DEC Alpha - Wikipedia empezando a ver algunas de las preocupaciones?

Al compilar a un lenguaje intermedio muchos de estos problemas desaparecen. Yo publico el código en lenguaje intermedio y se compila en la máquina en la que se va a ejecutar (o antes... más adelante). Esto también permite optimizar por máquina mientras el tiempo de ejecución lo hace. También es relativamente barato de hacer (es mucho más barato de compilar que el código en un lenguaje legible por humanos). Añadamos algunos beneficios adicionales como una mejor información de depuración disponible en tiempo de ejecución (¿alguna vez has visto una excepción? que preferirías que o "violación de acceso intentó leer 0xFFE10FF33") y de repente empieza a parecer bastante razonable.

Con el tiempo de ejecución hay otro beneficio y que tiene que ver con el uso de la memoria / fugas. Proporcionó un heap que soportaba la recolección de basura. Sí, todavía hay problemas y se puede pasar por alto!!! pero los problemas son relativamente raros en comparación con otros entornos como C++.

Una parte interesante es que originalmente el CLR tenía un enfoque secundario en no ejecutar dinámicamente con el tiempo de ejecución. Echa un vistazo a ngen Ngen.exe (Native Image Generator). Ngen permite generar imágenes nativas en tu máquina compilando antes de tiempo en tu máquina. También puede, obviamente, hacer algunas optimizaciones más, etc y su imagen todavía se puede ejecutar en múltiples arquitecturas.

Finalmente los lenguajes en el CLR podría terminar en algún lugar entre C y VB. En realidad se podría ser bastante flexible y "usar cosas administradas cuando tuviera sentido" y luego caer en algo de nivel más bajo para el código sensible al rendimiento (mientras que todavía se obtiene la mayor parte de los beneficios de la plataforma cruzada, etc para el código de nivel superior).

Quiero hacer hincapié en que no hay un simple "esta es la respuesta". Es más complejo. Hubo muchas facetas en la decisión... No fue una sola cosa lo que hizo que sucediera.