
[Lezioni di Programmazione] 💻 1 - Introduzione a C# e .NET 💻
Andiamo a capire di cosa parliamo quando ci riferiamo a .NET Framework e C#

Il framework .NET è l’infrastruttura che costituisce la nuova piattaforma creata da Microsoft per lo sviluppo di applicazioni component-based, n-tier, per internet, per l’accesso ai dati, per dispositivi mobili, o semplicemente per le classiche applicazioni desktop. La piattaforma .NET è composta da diverse tecnologie, strettamente accoppiate fra loro. Questo testo non vuole fornire una panoramica sull’intero universo .NET, ma focalizzerà l’attenzione sul linguaggio C#, linguaggio creato appositamente per il framework .NET. In questo capitolo vedremo comunque di introdurre le nozioni fondamentali dell’architettura, con lo scopo di porre il lettore in grado di comprendere il contesto in cui si troverà sviluppando con il linguaggio C#, e di effettuare quindi le migliori scelte possibili di progetto e di implementazione.
L'architettura .NET
L’architettura del framework .NET è illustrata nella figura 1.1. Essa si appoggia direttamente al sistema operativo, nella figura viene indicato Windows, ma esistono e sono anche a buon punto progetti per portare .NET su ambienti diversi, ad esempio Mono su Linux.
Il framework .NET consiste di cinque componenti fondamentali.
Il componente anima e cuore dell’intero framework, è il Common Language Runtime (CLR). Esso fornisce le funzionalità fondamentali per l’esecuzione di un’applicazione managed (gestita appunto dal CLR). Il CLR, a basso livello, si occupa inoltre dell’interfacciamento con il sistema operativo. Lo strato immediatamente al di sopra del CLR, è costituito dalla Base Class Library (o .NET Framework Class Library) di .NET, cioè un insieme di classi fondamentali, utili e necessarie a tutte le applicazioni ed a tutti gli sviluppatori. Ad esempio la BCL contiene i tipi primitivi, le classi per l’Input/Output, per il trattamento delle stringhe, per la connettività , o ancora per creare collezioni di oggetti. Dunque, per chi avesse esperienza con altre piattaforme, può essere pensato come un insieme di classi analogo a MFC, VCL, Java. Naturalmente altre classi specializzate saranno sicuramente mancanti nella BCL. Al di sopra della BCL, vengono quindi fornite le classi per l’accesso alle basi di dati e per la manipolazione dei dati XML, che, come vedrete iniziando a fare qualche esperimento, sono semplici da utilizzare ma estremamente potenti e produttive. Lo strato più alto del framework è costituito da quelle funzionalità che offrono un’interfacciamento con l’utente finale, ad esempio classi per la creazione di interfacce grafiche desktop, per applicazioni web, o per i sempre più diffusi web service.
- CLR
Il componente più importante del framework .NET è come detto il CLR, Common Language Runtime, che gestisce l’esecuzione dei programmi scritti per la piattaforma .NET. Chi viene dal linguaggio Java non farà fatica a pensare al CLR come ad una macchina virtuale del tutto simile concettualmente alla Java Virtual Machine che esegue bytecode Java. Il CLR si occupa dell’istanziazione degli oggetti, esegue dei controlli di sicurezza, ne segue tutto il ciclo di vita, ed al termine di esso esegue anche operazioni di pulizia e liberazione delle risorse. In .NET ogni programma scritto in un linguaggio supportato dal framework viene tradotto in un linguaggio intermedio comune, detto CIL (Common Intermediate Language) o brevemente IL, ed a questo punto esso può essere tradotto ed assemblato in un eseguibile .NET, specifico per la piattaforma su cui dovrà essere eseguito. In effetti, a run-time, il CLR non conosce e non vuole conoscere in quale linguaggio lo sviluppatore ha scritto il codice, il risultato della compilazione, è un modulo managed, indipendente dal linguaggio utilizzato, addirittura è possibile scrivere le applicazioni direttamente in linguaggio IL. La Figura 1.2 mostra il processo di compilazione del codice sorgente in moduli managed.
Un modulo managed contiene sia il codice IL che dei metadati. I metadati non sono altro che delle tabelle che descrivono i tipi ed i loro membri definiti nel codice sorgente, oltre ai tipi e relativi membri esterni referenziati nel codice. Il CLR però non esegue direttamente dei moduli, esso lavora con delle entità che sono chiamate assembly. Un assembly è un raggruppamento logico di moduli managed, e di altri file di risorse, ad esempio delle immagini utilizzate nell’applicazione, dei file html o altro ancora, ed in aggiunta a questi file, ogni assembly possiede un manifesto che descrive tutto il suo contenuto, ciò rende possibile il fatto che ogni assembly sia una unità autodescrittiva. I compilatori .NET, ad esempio il compilatore C#, attualmente creano un assembly in maniera automatica a partire dai moduli, aggiungendo il file manifesto. Un assembly può essere non solo un’eseguibile, ma anche una libreria DLL contenente una collezione di tipi utilizzabili eventuamente in altre applicazioni.
- La compilazione JIT
Il codice IL non è eseguibile direttamente dal microprocessore, almeno da quelli attualmente in commercio, e nemmeno il CLR può farlo, in quanto esso non ha funzioni di interprete. Dunque esso deve essere tradotto in codice nativo in base alle informazioni contenute nei metadati. Questo compito viene svolto a tempo di esecuzione dal compilatore JIT (Just In Time), altrimenti detto JITter. Il codice nativo viene prodotto on demand. Ad esempio la prima volta che viene invocato un metodo, esso viene compilato, e conservato in memoria. Alle successive chiamate il codice nativo sarà già disponibile in cache, risparmiando anche il tempo della compilazione just in time. Il vantaggio della compilazione JIT è che essa può essere realizzata dinamicamente in modo da trarre vantaggio dalla caratteristica del sistema sottostante. Ad esempio lo stesso codice IL può essere compilato in una macchina con un solo processore, ma potrà essere compilato in maniera differente su una macchina biprocessore, sfruttando interamente la presenza dei due processori. Ciò implica anche il fatto che lo stesso codice IL potrà essere utilizzato su sistemi operativi diversi, a patto che esista un CLR per tali sistemi operativi.
- CTS e CLS
Dati i differenti linguaggi che è possibile utilizzare per scrivere codice .NET compatibile, è necessario avere una serie di regole atte a garantirne l’interoperabilità , la compatibilità e l’integrazione dei linguaggi. Una classe scritta in C# deve essere utilizzabile in ogni altro linguaggio .NET, ed il concetto stesso di classe deve essere uguale nei diversi linguaggi, cioè una classe come intesa da C#, deve essere equivalente al concetto che ne ha VB.NET oppure C++ managed, o un altro linguaggio. Per permettere tutto questo, Microsoft ha sviluppato un insieme di tipi comuni, detto Common Type System (CTS), suddivisi in particolare, in due grandi categorie, tipi riferimento e tipi valore, ma come vedremo più in là nel testo, ogni tipo ha come primo antenato un tipo fondamentale, il tipo object, in tal modo tutto sarà considerabile come un oggetto. Il Common Type System definisce come i tipi vengono creati, dichiarati, utilizzati e gestiti direttamente dal CLR, e dunque in maniera ancora indipendente dal linguaggio. D’altra parte ogni linguaggio ha caratteristiche distintive, in più o in meno rispetto ad un altro. Se non fosse così, l’unica differenza sarebbe nella sintassi, cioè nel modo di scrivere i programmi. Un esempio chiarificante può essere il fatto che C# è un linguaggio case sensitive, mentre non lo è VB.NET. Per garantire allora l’integrazione fra i linguaggi è necessario stabilire delle regole, e nel far ciò Microsoft ha creato una specifica a cui tali linguaggi devono sottostare. Tale specifica è chiamata Common Language Specification (CLS). Naturalmente ogni linguaggio può anche utilizzare sue caratteristiche peculiari, e che non sono presenti in altri, in questo caso però il codice non sarà accessibile da un linguaggio che non possiede quella particolare caratteristica, nel caso contrario, cioè nel caso in cui, ad esempio, un componente è scritto facendo uso solo di caratteristiche definite dal CLS,. allora il componente stesso sarà detto CLS-compliant. Lo stesso CTS contiene tipi che non sono CLS-compliant, ad esempio il tipo UInt32, che definisce un intero senza segno a 32 bit, non è CLS compliant, in quanto non tutti i linguaggi hanno il concetto di intero senza segno.
- Gestione della Memoria
In linguaggi come C e C++, lo sviluppatore si deve occupare im prima persona della gestione della memoria, cioè della sua allocazione prima di poter creare ed utilizzare un oggetto e della sua deallocazione una volta che si è certi di non dover più utilizzarlo. Il CLR si occupa della gestione della memoria in maniera automatica, per mezzo del meccanismo di garbage collection. Tale meccanismo si occupa di tener traccia dei riferimenti ad ogni oggetto creato e che si trova in memoria, e quando l’oggetto non è più referenziato, cioè il suo ciclo di vita è terminato, il CLR si occupa di ripulirne le zone di memoria a questo punto non più utilizzate. Tutto ciò libera il programmatore da buona parte delle proprie responsabilità di liberare memoria non più utilizzata, e d’altra parte dalla possibilità di effettuare operazioni pericolose nella stessa memoria, andando per esempio a danneggiare dati importanti per altri parti del codice.