C#

Hello, World

Ci togliamo subito il pensiero del classico Hello World, che fa da delicato apripista per tutti i linguaggi di programmazione, il quale ci fornirà subito alcuni utili spunti in quanto molti dei concetti che qui presenteremo, pur nella loro semplicità, sono assolutamente fondamentali per la programmazione in C#:

  Esempio 3.1
1
2
3
4
5
6
7
8
9
using System;
class Test
{
  public static void Main()
  {
    Console.WriteLine("##### Salutiamo #####");
    Console.WriteLine("Hello, World!");
  }
}

La riga 1 introduce la parola chiave using che ci consente di utilizzare il namespace System. Che cosa è un namespace? Ne abbiamo già accennato nel capitolo introduttivo, ripetiamo qui il concetto. Esso in pratica definisce un ambito, una sezione, all’interno del quale possiamo definire entità di livello più specifico, una classe ad esempio oppure anche un altro namespace. In pratica siamo di fronte ad un guscio che definisce il perimetro del codice interno e lo delimita. Tale codice, come detto, può realizzare altre entità. Si realizza quindi una sorta di gioco di scatole cinesi ove all’interno di un namespace possiamo trovare le classi che a loro volta espongono metodi e proprietà. Per ora è importante capire che la funzione di questa entità è prevalentemente di tipo organizzativa proprio perchè al suo interno possiamo mettere praticamente di tutto garantendo nel contempo al contenuto una precisa, stabile ed univoca identità. Anche la libreria di classi di .Net, per fare un esempio nobile, è livellata gerarchicamente in namespace. Nell'esempio 2.1 occorre scrivere sullo standard output e questo è possibile utilizzando il metodo WriteLine che appartiene alla classe Console contenuta nel namespace System. Visivamente la situazione potrebbe essere così immaginata:




Qui vediamo il guscio costituito dal namespace System (la figura concettualmente si può applicare a qualsiasi namespace ricordando che sono ammessi anche altri namespace all'interno) che al suo interno contiene varie classi; tra di esse la classe Console che a sua volta contiene vari metodi tra cui quello che serve a noi, ovvero WriteLine. I nomi delle altre classi e degli altri metodi ovviamente sono fittizi. Come si vede la riga 1 contiene, nella sua semplicità, una certa complessità concettuale. La parola using, riassumendo, ci dà accesso ai namespace indicati, che siano predefiniti o che siano creati dal programmatore. In realtà tale keyword ha una valenza più generale (tecnicamente: permette di manipolare oggetti che implementano l'interfaccia IDisposable) ma è decisamente prematuro parlarne. Per il momento ci basta questo. Per capirne l'importanza basta eliminare la riga 1 e cercare di compilare il programma, il risultato sarà di questo tipo:

00001.cs(5,5): error CS0103: The name 'Console' does not exist in the current
context

00001.cs(6,5): error CS0103: The name 'Console' does not exist in the current
context

Chiaro? 00001.cs è il nome dato all'esempio (non gran che, lo so) alla riga 5 carattere 5 e alla riga 6 carattere 5 (rispetto al codice nell'esempio 3.1 manca la riga 1) il compilatore trova un nome di classe, Console, che non gli dice nulla, per lui non esiste in quanto lo abbiamo privato del riferimento al namespace che lo contiene, System. Reintegrate la riga 1 e tutto tornerà a funzionare. Come vedremo nel seguente esempio using potrebbe in realtà anche essere omesso ma questo ci provocherebbe molte verbosità in fase di scrittura del codice. Il suo scopo è quello di rendere immediatamente disponibile quello che invece andrebbe cercato....

La riga 2 è altrettanto importante in quanto introduce la definizione di classe. C# è un linguaggio a oggetti e tutto deve essere definito all'interno di classi che a loro volta, come visto, possono appartenere a namespace definiti dall'utente, esattamente come nella figura precedente. Delle classi parleremo ampiamente a suo tempo, l’importanza dell’argomento nella programmazione odierna è nota anche ai non esperti. La parola chiave che definisce una classe è evidentemente class alla quale segue immediatamente il nome della classe, in questo caso Test. E' buona norma fare iniziare con la lettera maiuscola i nomi delle classi, anche se non è strettamente obbligatorio.

La riga 3 ci dimostra che, come avviene in C/C++ e Java, anche in questo linguaggio vengono utilizzate le parentesi graffe per aprire e chiudere le "frasi", o meglio i blocchi di codice del programma. La graffa che chiude il blocco che si apre alla riga 3 si trova alla riga 9, mentre il blocco aperto alla riga 5 termina alla 8 come suggerisce anche l'indentazione. E' cosa buona indentare i propri programmi. Per quanto il compilatore non lo richieda nella maniera più assoluta l'occhio umano ringrazia sempre quando la leggibilità è migliore. Si evitano anche degli errori. Gli editor più comuni (no, Notepad no) supportano l’indentazione automatica della sintassi di C#. E la comodità di questo aspetto vale a maggior ragione quando, come nel pur banale esempio di sopra, si trovano blocchi di codice innestati in altri (i blocchi per l'appunto possono essere innestati uno nell'altro definendo una sorta di gerarchia a livelli). Ogni riga o meglio ogni singolo comando di un blocco deve terminare con il punto e virgola, vedasi ad esempio le righe 6 e 7, in assenza del quale sarà il compilatore a lamentarsi facendo chiaramente capire cosa si aspetta.

La riga 4 introduce una serie di parole chiave importanti (public è generalmente opzionale in casi come il 2.1) tra cui fondamentale è l'entry-point del programma (vi ricordate? Ne abbiamo già parlato nel capitolo introduttivo... eccolo qua) che è costituito dal metodo Main(). Il JIT cerca sempre quel particolare punto nel codice in cui è presente Main() quando deve mandare in esecuzione un programma. L'assenza di Main() viene salutata in maniera molto scorbutica dal compilatore (invece in linea teorica è possibile che più di una classe abbia un metodo che sia chiama Main ma in tal caso bisogna usare la direttiva /main del compilatore per avvisarlo su qual è il Main principale). L’entry point costituisce anche il punto di partenza per l’intero assembly a cui il programma appartiene. In questo senso, ovvero in presenza dell’entry point, è corretto parlare di applicazione e in particolare di dominio dell’applicazione che viene creato nel momento in cui questa viene eseguita. Le altre parole chiave per ora non consideratele ma ricordatevi che Main deve sempre essere preceduto da static; questo è obbligatorio, vedremo più avanti il perchè. Anche in questo caso potete provare a togliere tale keyword e vedere cosa vi risponde l'inflessibile compilatore; in breve vi accorgerete che vi chiede uno 'static Main' disponibile per poter iniziare a lavorare. Trovando il Main il runtime si occupa di creare l'application domain relativo (anche di questo argomento abbiamo accennato nel capitolo introduttivo)

La riga 5 dovrebbe esservi chiara mentre la 6 e la 7 sono una righe di codice operativo a tutti gli effetti. Avendo reso disponibile il namespace che contiene la classe Console possiamo usare anche i metodi di questa (i concetti relativi a classi e metodi saranno chiari molto più avanti; per adesso basti sapere che si tratta di funzioni che eseguono sequenze di istruzioni predefinite). Il punto più importante di tutto questo discorso, in questa fase è il . (punto) che compare come separatore tra Console e WriteLine. Nella sua apparente semplicità il punto è un importante operatore che permette gerarchicamente di navigare tra un entità e quelle che essa racchiude. formalmente dovendo scende da un namespace al metodo di una classe potremmo avere una istruzione di questo genere:

namespace.classe.metodo

Questo sarà messo in pratica nell'esempio 3.2. Insomma, grazie al pnto si passa da qualcosa che è, come dire, "più grosso" a qualche cosa di più preciso, definito, dal contenitore al contenuto eventualmente andando sempre più verso l'interno, nel rispetto della figura precedente riferita al namespace System.
Vediamo ora un secondo esempio che ci farà capire come evitare di dichiarare i namespace e perchè è meglio farlo:

  Esempio 3.2
1
2
3
4
5
6
7
8
class test

  public static void Main()
  {
    System.Console.WriteLine("###### Salutiamo ######");
    System.Console.WriteLine("Hello, World!");
  }
}

Vedete, manca la dichiarazione relativa all'uso del namespace ma le righe 5 e 6 lo richiamano direttamente (i namespace ovviamente sono noti al framework). Se per programmi così semplici la cosa può essere quasi indifferente, in casi di architetture di namespace più complesse e con programmi di grandi dimensioni chiaramente predichiarare i namespace necessari è irrinunciabile anche per leggibilità oltre che per un decisamente miglior livello di scrittura del codice. In tutti i linguaggi moderni esistono meccanismi di inclusione simili a questo d'altronde.
Per completezza sull'utilizzo di using almeno in questa fase introduttiva, si può anche aggiungere il seguente esempio, un sistema utilizzato da alcuni programmatori per migliorare la leggibilità del codice (miglioramento opinabile a parer mio):

  Esempio 3.3
1
2
3
4
5
6
7
8
9
using sulVideo = System.Console;
class test
{
  public static void Main()
  {
    sulVideo.WriteLine("###### Salutiamo ######");
    sulVideo.WriteLine("Hello, World!");
  }
}

In pratica è un meccanismo di alias che si può usare in vece del sistema comune. A me non sembra quel gran che come "commodity", visto che non rispecchia alcuno standard, ma è una possibilità in più che potete usare, se vi va a genio. La sua diffusione non mi pare sia molto ampia, quanto meno ho sempre visto una pratica molto limitata di questa "finezza" e per lo più ridotta ad ambiti operativi chiusi dove, probabilmente, avevano studiato una nomenclatura interna. Per il resto ben poche altre volte.

Un considerazione importante che fin qui non abbiamo fatto, ovvero che C# è un linguaggio case sensitive, come la maggioranza di quelli più usati attualmente. Quindi due nomi che differiscano solo per il fatto di avere una lettere corrispondente maiuscola in una e minuscola nell'altra sono di fatto diversi. Facilmente si capisce che "xx" e "XX" sono diversi ma anche "nome1" e "Nome1" sono diversi e "nOme1" è diverso dagli altri due. Questo è un fatto come detto comune in tanti linguaggi di programmazione ma da tenere ben presente, soprattutto all'inizio.

Abbiamo detto che la riga contenente il Main() è attorniato di altre parole; di static abbiamo accennato, public lo vedremo altrove, void merita ora qualche spiegazione. Nella definizione generale delle funzioni è previsto che queste possano restituire un valore verso l'esterno; lo vedremo meglio altrove ma già qui possiamo accennare all'importanza che questo avere per il nostro Main(). Si tratta, dal momento che è la procedura principale, di un modo sempolice con cui il programma può comunicare verso l'esterno. Comunemente Main() espone come valore void che significa "nulla, niente" nell'ambito di .Net ma altrettanto facilmente può esporre un valore significativo, precisamente un intero che ha come alias la parola int. Per chiarire la cosa possiamo modificare l'esempio 3.1 come segue:

  Esempio 3.4
1
2
3
4
5
6
7
8
9
10
using System;
class Test
{
  public static int Main()
  {
    Console.WriteLine("##### Salutiamo #####");
    Console.WriteLine("Hello, World!");
    return 0
  }
}

dove hanno fatto la loro comparsa quel int (intero) prima del Main e quel return alla riga 8 che in pratica restituisce uno 0. L'utilità di restituire dei valori in programmi semplici come questo è assolutamente nulla mentre può essere necessaria laddove vi siano dei codici particolari che devono essere comunicati in uscita o meglio verso l'esterno nel caso di sistemi cooperanti più complessi. Va ricordato ad esempio che, nel mondo Windows, esiste la variabile di sistema %ERRORLEVEL% che può essere utilizzata per memorizzare un codice di uscita da un programma. Tipico ad esempio è l'uso di questa variabile in batch script (i famosi files .bat che tutti più o meno conosciamo). In pratica lo useremo poco e comunque i valori di ritorno accettabili per la funzione Main() possono essere solo di tipo void e int. Questa limitazione come vedremo non è ovviamente presente per le altre funzioni.
 
Apriamo ora solo una parentesi per un doverso chiarimento: abbiamo infatti parlato di “metodo”. Ma che roba è un “metodo”? Ne parleremo ovviamente più avanti (lo so che è un po’ ripetitivo questo “più avanti” ma siamo solo al capitolo introduttivo), per ora ci limitiamo a dire che è un blocco di codice rigidamente dotato di un identificativo, un nome, detto terra terra, rigidamente delimitato, che contiene da 0 (caso limite, naturalmente, ma il compilatore lo permette) a n istruzioni con n grande a piacere, teoricamente, e che può essere richiamato attraverso il suo nome, sia pure con alcune regole da seguire; in generale è buona norma non esagerare con le dimensioni dei metodi per questioni di manutenibilità e leggibilità. Il nostro Main è un metodo come gli altri da un punto di vista costituzionale anche se, abbiamo visto, il suo ruolo è estremamente importante e unico nell'ambito di un programma.

Torniamo appunto adesso a parlare proprio di Main al quale possono essere passati dei parametri direttamente dalla riga di comando. Per quanto in ambiti GUI questa cosa sia spesso bypassata essa esiste comunque ed è parecchio utile. Modifichiamo il nostro esempio 2.1 come segue, anche se non è necessario che tutto sia chiaro fin da adesso  già nel prossimo capitolo tante cose si chiariranno:

  Esempio 3.5
1
2
3
4
5
6
7
8
using System;
class test
{
  public static void Main(string[] args)
  {
    Console.WriteLine("Parametro #1: " + args[0]);
  }
}

La riga 4 ci presenta dopo il Main la specifica di un array di stringhe che si chiama args (nome convenzionale, potreste metterci un altro nome a scelta);
args[0]
che trviamo invece alla riga 6 indica il primo elemento dell'array. Il tutto sarà più chiaro quando parleremo degli array e delle stringhe, per intanto ci basta sapere che, subito dopo aver richiamato il programma è possibile (nel caso specifico obbligatorio) mettere una stringa (che è una sequenza di caratteri alfanumerici) la quale sarà richiamata e stampata a video come richiesto alla riga 8. Si veda l'output di esempio:

D:\csprog>00003 pippo
Parametro #1: pippo

Ovvero: dopo aver scritto il nome del programma creato (nel mio caso 00003) aggiungiamo un parametro, quella stringa "pippo" che il programma intercetta ed elabora proprio alla riga 6 tramite, appunto, args[0]. Non vi preoccupate se non tutto è chiaro, caso mai è utile tornare sopra questo esempio tra qualche tempo. L'assenza del parametro in questo programma causa un crash (come è facile testare) non gestibile con le pochissime cognizioni fin qui apprese (lo sarà quando avremo parlato delle eccezioni). Da notare che abbiamo messo un parametro solo in questo caso ma possono essere n a piacere in quanto args è in grado di accettare un numero molto grande di valori. In pratica il primo parametro viene memorizzato in args[0], il secondo andrà in args[1], il terzo in args[2] e così via.

Nell'ottica di avere un po' di materiale in più per i prossimi paragrafi introduciamo una variante nell'esposizione e nella gestione dell'output da presentare nei nostri programmi. Vediamo subito l'esempio che analizzeremo di seguito:

  Esempio 3.6
1
2
3
4
5
6
7
8
9
10
11
12
using System;
class program
{
  public static void Main()
  {
    int x = 3;
    int y = 1;
    int z = 2;
    Console.WriteLine(x + "-" + y + "-" + z);
    Console.WriteLine("{0}-{1}-{2}", x, y, z);
  }
}

che presenta il seguente output:

3-1-2
3-1-2

Ovvero la riga 9 e la 10 stampano in modo identico. Se la riga 9 è, tutto sommato, in linea con quanto già visto, in aggiunta ci fa uso dell'operatore + che, come vedremo, può servire per unire, concatenare più stringhe, la 10 ci presenta, racchiusi tra parentesi graffe, 3 parametri ai quali, dopo la chiusura della stringa, corrispondono le 3 variabili, x, y e z. Esse vengono elaborate così’ come sono posizionati i parametri all’interno della stringa, che è compresa nella coppia dei doppi apici. Il primo parametro deve essere sempre 0, gli altri a seguire in sequenza aritmetica. Quindi al parametro 0 corrisponde la x, al parametro 1 la y ed al parametro 2 la z, posizionalmente, appunto. Questa è già una particolarità, parliamo della la parametrizzazione accettata in forma “variabile” per quanto riguarda il numero degli elementi da trattare da parte di Console.WriteLine; dei parametri passati in numero variabile discuteremo in generale in un altro capitolo.

Per chiudere questo paragrafo del tutto introduttivo vediamo un modo di salutarci un po' più interattivo e gradevole, visto che non si vive di solo command prompt e intanto vediamo all'operano i commenti che qui troviamo all'inizio del codice e alla riga 14. La riga 12 ci consente invece di catturare una stringa in input memorizzandola nella variabile nome; l'operatore = è quello che governa il processo di assegnazione ed attribuisce alla variabile alla sua sinistra il valore di ciò che si trova a destra purchè siano soddisfatti quei criteri, li vedremo, che rendono possibile l’attribuzione. Ovviamente ne riparleremo.

  Esempio 3.7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
Siamo nel mondo delle finestre
o no???
*/
using System;
using System.Windows.Forms;
class test
{
  public static void Main(string[] args)
  {
    Console.Write("Scrivi il tuo nome: ");
    string nome = Console.ReadLine();
    MessageBox.Show("Hello, " + nome); // la nostra prima finestra!!
  }
}

Come detto introduciamo qui i commenti. Dalla 1 alla 4 abbiamo un commento multilinea che viene posto all'interno di una coppia costituita da /* per aprire lo spazio dedicato al commento e */ per chiuderlo. La riga 13 invece ci presenta un commento che può stare solo su una riga, di seguito al codice o anche da solo; in questo caso è necessario usare la coppia //. Quanto al resto del programma ovviamente non import se ci capite poco (però using dovete sapere cosa fa...). Detto per inciso questo non è il modo migliore, ora ora come, per lavorare con le "finestre".

In ultima analisi potreste chiedervi: come è fatto un grosso programma C#? Comìè da un visto di vista per così dire "formale"? Giusto, anche se lo vedremo per gradi vale la pensa porsi il quesito. Strutturalmente potremmo avere spesso una situazione come la seguente:

using.....

class 1
{
.............
}

class 2
{
............
}

class 3
{
public static void Main()
........
}

..........
class n
{
.........
}

dopo la consueta sequenza dei namespace resi utilizzabili tramite using avremo la definizione delle varie classi che rappresenteranno le entità, i tipi di dato da noi definiti, con il loro corpo racchiuso all'interno delle immancabili parentesi graffe. Una classe, obbligatoriamente, conterrà il metodo Main, entry point del programma. Questa classe può essere posta ovunque nell'ambito del programma (in realtà dell'assembly). Eventualmente il tutto può essere racchiuso all'interno di un namespace che potrà essere richiamato da altri programmi che potranno quindi accedere alla classi pubbliche interne. Come si fa a racchiudere il tutto all'interno di un namespace? Semplicissimo, modifichiamo allo scopo il programma 3-1:

  Esempio 3.8
1
2
3
4
5
6
7
8
9
10
11
12
namespace MioNameSpace
{
  using System;
  class Test
  {
    public static void Main()
    {
      Console.WriteLine("##### Salutiamo #####");
      Console.WriteLine("Hello, World!");
    }
  }
}

Ovviamente un altro programma che voglia usare il namespace appena creato avrà la clausola:
using MioNameSpace

Abbiamo toccato vari argomenti in questo paragrafo, abbiamo parlato di stringhe, di classi ecc. Ovviamente tante cose non sono per nulla chiare, lo so. Vedremo tutto con calma per ora basta focalizzarsi sui concetti immediati e in particolare:

• I namespace
• la natura a classi dei programmi C#
• la navigazione gerarchica tra le varie entità in gioco, estremamente importante e per la quale bisognerà subito “fare l’occhio”
• il fatto che si tratta di un linguaggio case-sensitive
• le poche keyword che abbiamo presentato
• qualche concetto, come i metodi, le classi stesse, che riprenderemo ampiamente più avanti.