Presentazione del linguaggio C#

C# (pronunciato "See Sharp") è un linguaggio di programmazione moderno, orientato a oggetti e indipendente dai tipi. C# consente agli sviluppatori di compilare molti tipi di applicazioni sicure e affidabili eseguite in .NET. C# ha le sue radici nella famiglia di linguaggi C e risulterà immediatamente familiare ai programmatori di C, C++, Java e JavaScript. Questa presentazione offre una panoramica dei componenti principali del linguaggio in C# 8 e versioni precedenti. Se si vuole esplorare il linguaggio tramite esempi interattivi, provare l'introduzione alle esercitazioni su C #.

C# è un linguaggio di programmazione orientato a oggetti e orientato ai componenti. C# fornisce costrutti di linguaggio per supportare direttamente questi concetti, rendendo C# un linguaggio naturale in cui creare e usare componenti software. Dalla sua origine, C# ha aggiunto funzionalità per supportare nuovi carichi di lavoro e procedure di progettazione software emergenti. Alla base, C# è un linguaggio orientato a oggetti. È possibile definire i tipi e il relativo comportamento.

Diverse funzionalità C# consentono di creare applicazioni affidabili e durevoli. Garbage Collection recupera automaticamente la memoria occupata da oggetti inutilizzati non raggiungibili. I tipi nullable sono sorvegliati dalle variabili che non fanno riferimento a oggetti allocati. La gestione delle eccezioni offre un approccio strutturato ed estendibile al rilevamento e al ripristino degli errori. Le espressioni lambda supportano tecniche di programmazione funzionale. La sintassi LINQ (Language Integrated Query) crea un modello comune per l'uso dei dati da qualsiasi origine. Il supporto del linguaggio per le operazioni asincrone fornisce la sintassi per la compilazione di sistemi distribuiti. C# ha un sistema di tipi unificato. Tutti i tipi C#, inclusi i tipi di primitiva quali int e double, ereditano da un unico tipo object radice. Tutti i tipi condividono un set di operazioni comuni. I valori di qualsiasi tipo possono essere archiviati, trasportati e gestiti in modo coerente. C# supporta inoltre sia i tipi riferimento definiti dall'utente che i tipi valore. C# consente l'allocazione dinamica di oggetti e l'archiviazione in linea di strutture leggere. C# supporta tipi e metodi generici, che offrono maggiore sicurezza dei tipi e prestazioni. C# fornisce iteratori che consentono agli implementatori di classi di raccolta di definire comportamenti personalizzati per il codice client.

C# enfatizza il controllo delle versioni per garantire che i programmi e le librerie possano evolversi nel tempo in modo compatibile. Gli aspetti della progettazione di C# influenzati direttamente dalle considerazioni sul controllo delle versioni includono i modificatori e separati, le regole per la risoluzione dell'overload virtualoverride del metodo e il supporto per dichiarazioni esplicite dei membri di interfaccia.

Architettura .NET

I programmi C# vengono eseguiti in .NET, un sistema di esecuzione virtuale denominato Common Language Runtime (CLR) e un set di librerie di classi. CLR è l'implementazione da parte di Microsoft dell'interfaccia della riga di comando, uno standard internazionale. L'interfaccia della riga di comando è la base per la creazione di ambienti di esecuzione e sviluppo in cui linguaggi e librerie si integrano senza problemi.

Il codice sorgente scritto in C# viene compilato in un linguaggio intermedio conforme alla specifica dell'interfaccia della riga di comando. Il codice IL e le risorse, ad esempio bitmap e stringhe, vengono archiviati in un assembly, in genere con un'estensione .dll. Un assembly contiene un manifesto che fornisce informazioni su tipi, versione e impostazioni cultura dell'assembly.

Quando viene eseguito il programma C#, l'assembly viene caricato in CLR. CLR esegue la compilazione JIT per convertire il codice IL in istruzioni del computer nativo. CLR fornisce altri servizi correlati all'operazione di Garbage Collection automatica, alla gestione delle eccezioni e alla gestione delle risorse. Il codice eseguito da CLR viene talvolta definito "codice gestito". Il "codice non gestito" viene compilato nel linguaggio macchina nativo destinato a una piattaforma specifica.

L'interoperabilità del linguaggio è una funzionalità chiave di .NET. Il codice IL prodotto dal compilatore C# è conforme a Common Type Specification (CTS). Il codice IL generato da C# può interagire con il codice generato dalle versioni .NET di F#, Visual Basic, C++. Sono disponibili più di 20 altri linguaggi conformi a CTS. Un singolo assembly può contenere più moduli scritti in linguaggi .NET diversi. I tipi possono fare riferimento tra loro come se fossero scritti nello stesso linguaggio.

Oltre ai servizi in fase di esecuzione, .NET include anche librerie estese. Queste librerie supportano molti carichi di lavoro diversi. Sono organizzati in spazi dei nomi che forniscono un'ampia gamma di funzionalità utili. Le librerie includono tutto, dall'input e dall'output del file alla manipolazione di stringhe all'analisi XML, ai framework dell'applicazione Web Windows form. La tipica applicazione C# usa ampiamente la libreria di classi .NET per gestire le attività comuni di "plumbing".

Per altre informazioni su .NET, vedere Panoramica di .NET.

Hello world

Il programma "Hello World" viene tradizionalmente usato per presentare un linguaggio di programmazione. Di seguito è riportato il programma Hello, World in C#:

using System;

class Hello
{
    static void Main()
    {
        Console.WriteLine("Hello, World");
    }
}

Il programma "Hello World" inizia con una direttiva using che fa riferimento allo spazio dei nomi System. Gli spazi dei nomi consentono di organizzare i programmi e le librerie C# in modo gerarchico. Gli spazi dei nomi contengono tipi e altri spazi dei nomi. Lo stazio dei nomi System, ad esempio, contiene diversi tipi, come la classe Console a cui viene fatto riferimento nel programma, e altri spazi dei nomi, come IO e Collections. Una direttiva using che fa riferimento a un determinato spazio dei nomi consente l'uso non qualificato dei tipi che sono membri di tale spazio dei nomi. Grazie alla direttiva using, il programma può usare Console.WriteLine come sintassi abbreviata per System.Console.WriteLine.

La classe Hello dichiarata dal programma "Hello World" ha un solo membro, ovvero il metodo denominato Main. Il Main metodo viene dichiarato con il modificatore static . Mentre i metodi di istanza possono fare riferimento a una particolare istanza dell'oggetto contenitore usando la parola chiave this, i metodi statici operano senza riferimento a un determinato oggetto. Per convenzione, un metodo statico denominato Main funge da punto di ingresso di un programma C#.

L'output del programma viene prodotto dal metodo WriteLine della classe Console nello spazio dei nomi System. Questa classe viene fornita da librerie di classi standard a cui, per impostazione predefinita, fa automaticamente riferimento il compilatore.

Tipi e variabili

Un tipo definisce la struttura e il comportamento di tutti i dati in C#. La dichiarazione di un tipo può includere i membri, il tipo di base, le interfacce implementate e le operazioni consentite per tale tipo. Una variabile è un'etichetta che fa riferimento a un'istanza di un tipo specifico.

In C# esistono due generi di tipi: tipi valore e tipi riferimento. Le variabili dei tipi valore contengono direttamente i dati. Le variabili dei tipi riferimento archiviano i riferimenti ai dati, questi ultimi sono noti come oggetti. Con i tipi riferimento, è possibile che due variabili fanno riferimento allo stesso oggetto e che le operazioni su una variabile influiscano sull'oggetto a cui fa riferimento l'altra variabile. Con i tipi valore, le variabili hanno ognuna una propria copia dei dati e non è possibile che le operazioni su uno influiscano sull'altra ( ref ad eccezione delle variabili di out parametro e ).

Un identificatore è un nome di variabile. Un identificatore è una sequenza di caratteri Unicode senza spazi vuoti. Un identificatore può essere una parola riservata C#, se è preceduta da @. L'uso di una parola riservata come identificatore può essere utile quando si interagisce con altre lingue.

I tipi valore di C# sono ulteriormente suddivisi in tipi semplici, tipienum, tipi struct, tipi valore nullable e tipi di valore tupla. I tipi riferimento di C# sono ulteriormente suddivisi in tipi di classe, tipi di interfaccia, tipi di matrice e tipi delegati.

La struttura seguente offre una panoramica del sistema di tipi di C#.

I programmi C# usano le dichiarazioni di tipo per creare nuovi tipi. Una dichiarazione di tipo consente di specificare il nome e i membri del nuovo tipo. Sei categorie di tipi di C# sono definibili dall'utente: tipi classe, tipi struct, tipi di interfaccia, tipi enum, tipi delegati e tipi valore di tupla. È anche possibile dichiarare record tipi, record structo record class. I tipi di record hanno membri sintetizzati dal compilatore. I record vengono utilizzati principalmente per archiviare i valori, con un comportamento minimo associato.

  • Un tipo class definisce una struttura dati contenente membri dati (campi) e membri funzione (metodi, proprietà e altro). I tipi classe supportano l'ereditarietà singola e il polimorfismo, meccanismi in base ai quali le classi derivate possono estendere e specializzare le classi di base.
  • Un tipo struct è simile a un tipo classe in quanto rappresenta una struttura con membri dati e membri funzione. Tuttavia, a differenza delle classi, gli struct sono tipi valore e in genere non richiedono l'allocazione dell'heap. I tipi struct non supportano l'ereditarietà specificata dall'utente e tutti i tipi struct ereditano in modo implicito dal tipo object.
  • Un interface tipo definisce un contratto come set denominato di membri pubblici. Un class oggetto struct o che implementa un interface oggetto deve fornire implementazioni dei membri dell'interfaccia. Un tipo interface può ereditare da più interfacce di base e un tipo class o struct può implementare più interfacce.
  • Un tipo delegate rappresenta i riferimenti ai metodi, con un elenco di parametri e un tipo restituito particolari. I delegati consentono di trattare i metodi come entità che è possibile assegnare a variabili e passare come parametri. I delegati sono analoghi ai tipi funzione forniti dai linguaggi funzionali. Sono anche simili al concetto di puntatori a funzione presenti in altri linguaggi. A differenza dei puntatori a funzione, i delegati sono orientati a oggetti e indipendente dai tipi.

I classtipi struct, interface, e supportano delegate tutti i generics, in base ai quali possono essere parametrizzati con altri tipi.

C# supporta matrici unidimensionali e multidimensionali di qualsiasi tipo. A differenza dei tipi elencati in precedenza, i tipi di matrice non devono essere dichiarati prima di poter essere usati. Al contrario, i tipi matrice vengono costruiti facendo seguire a un nome di tipo delle parentesi quadre. Ad esempio, int[]intè una matrice unidimensionale di , int[,]intè una matrice bidimensionale di e int[][] è una matrice unidimensionale di matrici unidimensionali, o una matrice "irregolare", di int.

I tipi nullable non richiedono una definizione separata. Per ogni tipo non nullable Tè presente un tipo nullable corrispondente T?, che può contenere un valore aggiuntivo, null. Ad esempio, int? è un tipo che può contenere qualsiasi integer a 32 bit nullo il valore e string? è un tipo che può contenere string qualsiasi o il valore null.

Il sistema di tipi di C# è unificato in modo che un valore di qualsiasi tipo possa essere considerato come object. In C# ogni tipo deriva direttamente o indirettamente dal tipo classe object e object è la classe di base principale di tutti i tipi. I valori dei tipi riferimento vengono trattati come oggetti semplicemente visualizzando tali valori come tipi object. I valori dei tipi valore vengono trattati come oggetti mediante l'esecuzione di operazioni di boxing e unboxing. Nell'esempio seguente un valore int viene convertito in object e quindi convertito nuovamente in int.

int i = 123;
object o = i;    // Boxing
int j = (int)o;  // Unboxing

Quando un valore di un tipo valore viene assegnato a un object riferimento, viene allocata una "casella" per contenere il valore. Tale casella è un'istanza di un tipo riferimento e il valore viene copiato in tale casella. Viceversa, quando si esegue il object cast di un riferimento a un tipo valore, object viene eseguito un controllo che l'oggetto a cui si fa riferimento sia una casella del tipo valore corretto. Se il controllo ha esito positivo, il valore nella casella viene copiato nel tipo di valore.

Il sistema di tipi unificato di C# significa che i tipi valore vengono considerati come riferimenti object "su richiesta". A causa dell'unificazione, le librerie per utilizzo objectobjectgenerico che usano il tipo possono essere usate con tutti i tipi che derivano da , inclusi i tipi riferimento e i tipi valore.

In C# sono disponibili diversi tipi di variabili, inclusi campi, elementi matrice, variabili locali e parametri. Le variabili rappresentano posizioni di archiviazione. Ogni variabile ha un tipo che determina quali valori possono essere archiviati nella variabile, come illustrato di seguito.

  • Tipo valore non-nullable
    • Valore esattamente del tipo indicato
  • Tipo valore nullable
    • Valore null o valore esattamente del tipo indicato
  • object
    • Riferimento null, riferimento a un oggetto di qualsiasi tipo riferimento oppure riferimento a un valore boxed di qualsiasi tipo valore
  • Tipo classe
    • Riferimento null, riferimento a un'istanza del tipo classe oppure riferimento a un'istanza di una classe derivata dal tipo classe
  • Tipo interfaccia
    • Riferimento null, riferimento a un'istanza di un tipo classe che implementa il tipo interfaccia oppure riferimento a un valore boxed di un tipo valore che implementa il tipo interfaccia
  • Tipo matrice
    • Riferimento null, riferimento a un'istanza del tipo matrice oppure riferimento a un'istanza di un tipo matrice compatibile
  • Tipo delegato
    • Riferimento null oppure riferimento a un'istanza di un tipo delegato compatibile

Struttura del programma

I concetti organizzativi principali in C# sono programmi, spazi dei nomi, tipi, membrie assembly. I programmi dichiarano i tipi, che contengono i membri e possono essere organizzati in spazi dei nomi. Classi, struct e interfacce sono esempi di tipi. I campi, i metodi, le proprietà e gli eventi sono esempi di membri. Quando i programmi C# vengono compilati, vengono fisicamente suddivisi in assembly. Gli assembly hanno in genere l'estensione di .exe file o .dll, a seconda che .exe rispettivamente applicazioni o librerie.

Come piccolo esempio, si consideri un assembly che contiene il codice seguente:

namespace Acme.Collections;

public class Stack<T>
{
    Entry _top;

    public void Push(T data)
    {
        _top = new Entry(_top, data);
    }

    public T Pop()
    {
        if (_top == null)
        {
            throw new InvalidOperationException();
        }
        T result = _top.Data;
        _top = _top.Next;

        return result;
    }

    class Entry
    {
        public Entry Next { get; set; }
        public T Data { get; set; }

        public Entry(Entry next, T data)
        {
            Next = next;
            Data = data;
        }
    }
}

Il nome completo di questa classe è Acme.Collections.Stack. La classe contiene vari membri: un campo _top, due metodi Push e Pop e una classe annidata Entry. La Entry classe contiene inoltre tre membri: una proprietà denominata Next, una proprietà denominata Datae un costruttore. è Stack una Stack . Ha un parametro di tipo, T che viene sostituito con un tipo concreto quando viene usato.

Uno stack è una raccolta "first in - last out". I nuovi elementi vengono aggiunti all'inizio dello stack. Quando un elemento viene rimosso, viene rimosso dall'inizio dello stack. Nell'esempio precedente viene dichiarato il Stack tipo che definisce l'archiviazione e il comportamento per uno stack. È possibile dichiarare una variabile che fa riferimento a un'istanza del tipo Stack per usare tale funzionalità.

Gli assembly contengono codice eseguibile sotto forma di istruzioni di linguaggio intermedio (Intermediate Language, IL) e informazioni simboliche sotto forma di metadati. Prima dell'esecuzione, il compilatore JIT (Just-In-Time) di .NET Common Language Runtime converte il codice IL in un assembly in codice specifico del processore.

Poiché un assembly è un'unità #include di funzionalità autodescritta contenente sia il codice che i metadati, non è necessario utilizzare direttive e file di intestazione in C#. I membri e i tipi pubblici contenuti in un determinato assembly vengono resi disponibili in un programma C# semplicemente facendo riferimento a tale assembly durante la compilazione del programma. Questo programma usa ad esempio la classe Acme.Collections.Stack dell'assembly acme.dll:

class Example
{
    public static void Main()
    {
        var s = new Acme.Collections.Stack<int>();
        s.Push(1); // stack contains 1
        s.Push(10); // stack contains 1, 10
        s.Push(100); // stack contains 1, 10, 100
        Console.WriteLine(s.Pop()); // stack contains 1, 10
        Console.WriteLine(s.Pop()); // stack contains 1
        Console.WriteLine(s.Pop()); // stack is empty
    }
}

Per compilare questo programma, è necessario fare riferimento all'assembly contenente la classe stack definita nell'esempio precedente.

I programmi C# possono essere archiviati in diversi file di origine. Quando viene compilato un programma C#, tutti i file di origine vengono elaborati insieme e i file di origine possono fare liberamente riferimento l'uno all'altro. Concettualmente, è come se tutti i file di origine fossero concatenati in un unico file di grandi dimensioni prima dell'elaborazione. Le dichiarazioni con inoltro non sono mai necessarie in C# perché, con poche eccezioni, l'ordine delle dichiarazioni non è significativo. C# non limita un file di origine alla dichiarazione di un solo tipo pubblico né richiede che il nome del file di origine corrisponda a un tipo dichiarato nel file di origine.

Altri articoli di questa presentazione illustrano questi blocchi organizzativi.