Quindi praticamente tutte le risposte qui riguardano la portabilità futura del codice, che non è realmente coinvolta. La ragione per cui girava in una VM è in gran parte che ci sono dei benefici nell'eseguire in una VM!
All'inizio di .NET c'era C#/VB.NET/C++(cli)/etc. Scrivere codice per essere in grado di interagire tra loro non sarebbe stato così difficile al contrario di produrre l'intero CLR e le varie specifiche che lo accompagnavano. Perché formalizzare l'IL se il nostro unico obiettivo è quello di permettere chiamate cross language etc?
Il punto di avere tutti compilati nello stesso linguaggio intermedio (ok il mixed mode C++ è un outlier) era un po' diverso. C'è una ragione davvero ovvia per cui le cose sono andate in questa direzione. Se si guarda indietro storicamente al tempo che precede il 2000, c'erano serie domande sul fatto che x86 sarebbe stata "l'architettura" in futuro (un bel po' di standard concorrenti stavano arrivando/andando, anche windows supportava più di uno!) e un grande punto dolente era far funzionare il codice su più ambienti (ovviamente anche il jvm aveva questo come obiettivo). C'erano comunque altri problemi. Uno davvero grande e lampante era la memoria: guardiamo le dimensioni approssimative della memoria sulle macchine ...
'82 a '84 - da 1KB a 16KB
'85 a '89 - da 512KB a 640KB
'89 a '92 - da 1MB a 2MB
'93 a '94 - da 4MB a 8MB
'95 a '99 - da 32MB a 128MB
'00 a '01 - da 256MB a 512MB
'02 a '04 - da 1GB a 2GB
'05 a '09 - da 3GB a 4GB
'10 a oggi - da 6GB a 48GB
Se tu fossi seduto a metà -> fine anni 90 quale sarebbe uno dei tuoi principali pensieri?! 32 bit vs 64 bit sembra proprio un grosso problema, vero 😉 Da notare che da 256mb->2gb sono passati +-4 anni! Probabilmente come azienda come Microsoft avreste grossi problemi di servizio clienti del tipo "Salve, ho scaricato questo software e non può funzionare sulla mia macchina".
C'erano molteplici soluzioni nello spazio 32/64. Alcune erano nuove architetture di processori, altre, come quella di Intel (beh, una delle intel!), erano istruzioni aggiunte. Anche se fosse esistito un hardware migliore (e Microsoft avesse scaricato gli altri processori che supportava) c'era ancora il problema dell'uso diffuso di due sistemi diversi. Ora per un sistema operativo questo non è un grosso problema. Che dire della vostra app che scatta una foto e ci mette sopra un cappello da strega? Avreste bisogno di compilare per almeno due target (con altre architetture IA64 (ISA) ecc che entrano in gioco, di quante compilazioni avete bisogno?) Ora immaginate che ogni team che scrive un'applicazione in casa/prodotto/ecc debba fare questo. Avreste anche bisogno di testare il vostro prodotto sulle varie architetture ecc. no? Iniziate a vedere il problema? Il singolo problema più grande qui, però, a parte il multiplo... è l'incertezza. Quale sarebbe stato lo standard tra 5 anni? Cosa succederà quando uscirà un nuovo processore?
Come nota a margine, sapevate che windows girava anche sui chip DEC alpha? DEC Alpha - Wikipedia cominciando a vedere alcune delle preoccupazioni?
Compilando in un linguaggio intermedio molti di questi problemi vanno via. Rilascio il codice in linguaggio intermedio e viene compilato sulla macchina su cui verrà eseguito mentre (o prima ... più avanti) viene eseguito, abbastanza pulito eh? Questo permette anche le ottimizzazioni per macchina mentre il runtime le sta facendo! È anche relativamente economico da fare (è molto più economico compilare che codificare in un linguaggio leggibile dall'uomo). Aggiungiamo anche alcuni benefici come una migliore informazione di debug disponibile a runtime (avete mai visto un'eccezione? che preferireste o "violazione di accesso tentato per leggere 0xFFE10FF33") e improvvisamente inizia a sembrare abbastanza ragionevole.
Con il runtime c'è un altro beneficio e ha a che fare con l'uso della memoria/leaks. Ha fornito un heap che supporta la garbage collection. Sì, ci sono ancora problemi e si può bypassare!!! ma i problemi sono relativamente rari rispetto ad altri ambienti come C++.
Un aspetto interessante è che originariamente il CLR aveva un focus secondario sul non eseguire dinamicamente con il runtime. Date un'occhiata a ngen Ngen.exe (Native Image Generator). Ngen permette di generare immagini native sulla vostra macchina compilando in anticipo sulla vostra macchina. Ovviamente puoi anche fare qualche ottimizzazione in più, ecc. e la tua immagine può ancora essere eseguita su più architetture.
Finalmente i linguaggi sul CLR potrebbero finire da qualche parte tra C e VB. Si potrebbe essere abbastanza flessibili e "usare la roba gestita quando ha senso" e poi passare a qualcosa di livello più basso per il codice sensibile alle prestazioni (mentre si ottengono ancora la maggior parte dei benefici della piattaforma incrociata ecc. per il codice di livello superiore).
Voglio sottolineare che non c'è una semplice risposta "questa è la risposta". È più complesso. Ci sono state molte sfaccettature nella decisione... Non è stata una sola cosa a farla accadere.