Simone Santoro

[Lezioni di Programmazione] đź’» 1 - Introduzione a C# e .NET đź’»

2019-10-26 15:51:17

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.

Nella prossima lezione andremo a creare il nostro primo programma in C#

Link eBook Completo