Sei sulla pagina 1di 65

1.

Introduzione a C#

C#, si legge «c sharp», può essere considerato il linguaggio di programmazione per eccellenza del
Framework .NET: diversamente dagli altri linguaggi, come Visual Basic o C++, esso è nato
espressamente per la nuova piattaforma. In questo senso, è significativo il fatto che Microsoft stessa
si sia servita di C# per scrivere gran parte delle librerie di .NET .

Uno degli slogan che hanno accompagnato C# fin dalla sua nascita lo presenta come «un
linguaggio facile come Java, potente come il C++». In effetti, come vedremo, le somiglianze con
i due linguaggi sono molte, e non solo dal punto di vista della sintassi.

Le differenze comunque ci sono ma la loro trattazione esula dagli obiettivi di questo corso, che si
propone, invece, di analizzare nello specifico il linguaggio C#.

Chi fosse interessato a differenze e somilgianze, può trovare maggiori informazioni usando i
collegamenti a fine lezione (in lingua inglese) oppure, avendo a disposizione Visual Studio 2005, si
possono trovare maggiori dettagli sull'argomento cercando «Comparison Between Java and C#» e
«Comparison Between C++ and C#» nella guida in linea.

Questa guida, in una prima fase, introdurrà ai concetti chiave di questo linguaggio, nella seconda
parte del corso invece, ci si concentrerà sull'ambiente di sviluppo Visual Studio 2005 e sulle nuove
funzionalità introdotte dalla versione 2.0 del Framework. Infine fornirà una panoramica degli
strumenti messi a disposizione per la realizzazione di applicazioni web con ASP .NET.

Il corso si rivolge a chi ha già dimestichezza con la programmazione ad oggetti e con linguaggi
simili a Java o C++.

Sono volutamente tralasciate le spiegazioni sulla sintassi di base: per esempio i costrutti if, switch,
for, while, etc. Chi già conosce linguaggi come c++, Java, php o Javascript non vedrà grosse novità.
Chi invece non avesse familiarità con i linguaggi cosiddetti "c-like" può trovare in fondo alla
lezione un link per approfondire.

In ogni caso, dove necessario per la comprensione del corso, saranno indicati esplicitamente i
collegamenti con i concetti trattati su altre fonti.

In queste prime pagine ci occuperemo delle caratteristiche proprie di C# e delle innovazioni che
introduce nel modo di programmare.

Al momento della stesura di questa guida, il Framework .NET 2.0 e Visual Studio 2005 sono
ancora in fase di beta test (in particolare, è stato usato Visual Studio 2005 Beta 2), quindi è
possibile, anche se in linea di massima non dovrebbe accadere, che alcune delle caratteristiche
mostrate saranno diverse dalla versione finale.

Poiché la versione italiana verrà rilasciata solo dopo il rilascio ufficiale del prodotto, si è fatto
riferimento alle voci dei menù e delle finestre della versione inglese; ove possibile, è stata fornita
una traduzione, che però in alcuni casi potrebbe non corrispondere con l'effettiva localizzazione in
italiano.
Questa guida si propone di suscitare curiosità sul mondo di C#, fornendo però gli strumenti minimi
necessari ad approfondire individualmente gli argomenti affrontati.

2.C# e VB .NET a confronto


C# e Visual Basic .NET sono due tra i molti linguaggi di programmazione disponibili per la
piattaforma .NET. Il Common Language Runtime (CLR) del Framework .NET costituisce, infatti,
una piattaforma di esecuzione comune per un gran numero di linguaggi, tra cui C++, J#, Cobol.NET
.

Più che stabilire quale sia il "migliore", è interessante capire le peculiarità di ciascun linguaggio. Ci
soffermeremo sulla valutazione delle differenze più importanti.

Ad una prima analisi è evidente che VB.NET risulta la scelta preferita per chi proviene dalle
versioni precedenti di VB e ASP-VBScript, mentre C# è più accattivante per chi proviene da
esperienze con C++ e Java. La sintassi di C# assomiglia moltissimo a quella di Java, mentre quella
di VB.NET è evoluzione del Visual Basic.

La differenza più importante nel passaggio dal vecchio al nuovo non riguarda tanto la sintassi
quanto la sensazione di novità in termini di approccio alla programmazione ad oggetti.

Chi è abituato a lavorare in C++ o Java trova un ambiente in cui i concetti sono analoghi quando
non del tutto sovrapposti. Per esempio in .NET come in Java tutto è un oggetto: è abbandonato ogni
tipo di approccio ibrido con la programmazione strutturata in stile C++.

Quindi chi proviene dai "vecchi" ASP e VB si trova davanti ad un linguaggio simile in alcuni
aspetti della sintassi di base ma con un approccio alla programmazione a oggetti che è comune a
tutto il Framework ed è più proprio di Java e compagnia.

C# è un linguaggio nuovo, mentre VB .NET porta con sé l'eredità di tutte le versioni precedenti di
Visual Basic (la sintassi di VB .NET, seppur aggiornata con il supporto agli oggetti, è praticamente
la stessa di quella di Visual Basic 4.0, rilasciato nell'ormai lontano 1996).

Un primo aspetto evidente è che il codice scritto in VB .NET risulta più lungo rispetto
all'equivalente in C#. Consideriamo, per esempio, una funzione che prende in ingresso due numeri e
ne calcola la media:

//C#
public double CalcolaMedia(double N1, double N2)
{
return (N1 + N2) / 2;
}

'Visual Basic .NET


Public Function CalcolaMedia(ByVal N1 As Double, ByVal N2 As Double) As Double

Return (N1 + N2) / 2


End Function
Un'altra grande differenza, forse la più evidente, è che C# è case sensitive, ovvero fa distinzione tra
maiuscole e minuscole, mentre VB case insensitive: per C# le variabili nomePersona e
NomePersona sono diverse, mentre per Visual Basic .NET sono la stessa.

Al di là delle differenze che riguardano la sintassi, C# permette di realizzare cose che in VB .NET
non si possono fare, e viceversa. Ad esempio, C#, mostrando la sua derivazione dal C++, consente
di utilizzare i puntatori, cosa che VB .NET non permette.

Alcune differenze architetturali sono state colmate con la versione 2.0 del Framework, che
introduce anche per Visual Basic .NET l'overloading degli operatori (altra caratteristica che C# ha
preso da C++) e la possibilità di documentare il codice scritto utilizzando il linguaggio XML. Infatti
C# permette la documentazione delle classi usando dei tag nel codice in modo molto somigliante ai
commenti per JavaDocs.

VB .NET, in particolare con la nuova versione del Framework, rende ancora più semplice e veloce
la scrittura di applicazioni: tra i tanti esempi che si potrebbero fare a riguardo, citiamo il namespace
My , che fornisce un rapido accesso a tutta una serie di proprietà e metodi di uso comune,
consentendo uno sviluppo più efficiente.

Le informazioni contenute nel namespace My sono comunque disponibili anche da C#, ma la sua
presenza in VB .NET rende molto più agevole lo svolgimento di alcuni compiti. Ad esempio, per
scaricare un file da Internet, usando Visual Basic .NET con il Framework 2.0 è sufficiente
un'istruzione:

My.Computer.Network.DownloadFile("http://www.html.it/file.zip", "C:/Test")

In C# non esiste un oggetto My e dovremmo istanziare noi un oggetto della classe Network per
ottenere lo stesso effetto.

Inoltre, l'elenco dei membri che appare grazie all'IntelliSense in VB .NET è diviso in due schede in
cui vengono messi in evidenza gli elementi di uso comune, facilitando la selezione della voce
desiderata. In C# l'elenco dei membri viene sempre visualizzato nella sua interezza.

Figura 1: L'IntelliSense di Visual Basic .NET

Questa rapida carrellata sulle differenze tra C# e Visual Basic .NET non ha la pretesa di essere
esaustiva: per maggiori informazioni (riguardanti soprattutto le più evidenti diversità di sintassi),
consultare i collegamenti a fine pagina. Inoltre, disponendo di Visual Studio 2005, è possibile
ottenere maggiori dettagli sull'argomento cercando Language Equivalents nella guida in linea.

3.Il primo programma


Il .NET framework SDK, ovvero l'insieme di tutti gli strumenti necessari alla creazione di
software per la piattaforma .NET, è liberamente scaricabile dal sito Microsoft.

Dunque è sufficiente usare un editor di testi per sviluppare applicazioni .NET, anche se quando ci
si orientasse verso programmi più complessi e dotati di interfaccia grafica, l'adozione di un
ambiente di sviluppo visuale diventa quasi indispensabile.

Realizziamo subito il nostro primo programma. Costruiremo un'applicazione "console", ovvero


eseguibile dal prompt dei comandi.

Per scrivere un programma di questo tipo non è strettamente necessario un ambiente di sviluppo
come Visual Studio (che analizzeremo in dettaglio nelle prossime Lezioni), data l'assenza di
elementi grafici o di connessioni complesse tra classi.

Pensiamo di voler realizzare la classica applicazione che stampa a video la stringa "Ciao Mondo!",
aspetta che l'utente prema il tasto INVIO e, quindi, termina. Il codice è il seguente:

public class HelloWorld {

public static void Main()


{
System.Console.WriteLine("Ciao Mondo!");
System.Console.ReadLine();
}
}

Chi ha già una conoscenza di Java potrà notare una certa somiglianza, dal punto di vista della
sintassi, tra i due linguaggi. Per eseguire il programma, salviamolo in un file di testo con nome, ad
esempio, HelloWorld.cs. Lìestensione «.cs» contraddistingue i file sorgenti di C#.

Con la versione 2.0 di .NET, nel menù Start di Windows comparirà il gruppo Microsoft .NET
Framework SDK v2.0, al cui interno si trova il comando SDK Command Prompt che permette di
aprire un prompt dei comandi con le variabili di sistema per l'uso degli strumenti a riga di comando
del Framework già impostate.

Quindi possiamo spostarci nella cartella in cui è stato salvato il file «Hello.cs» e digitare il
comando:

>csc Hello.cs

csc.exe è il compilatore C#, il risultato di questa operazione è un file eseguibile di nome


«Hello.exe». Per avviare il frutto delle nostre fatiche è sufficiente digitare:
>Hello

Figura 1. L'output del programma

Iniziamo a prendere dimestichezza con la struttura di un programma C# esaminando le poche righe


di questo esempio.

L'espressione «public class HelloWorld» serve per definire una classe di nome «HelloWorld».
Quello di classe è un concetto fondamentale, che approfondiremo nel corso delle prossime lezioni,
al momento basti sapere che tutto il codice di un programma C# definisce classi e scambi di
informazioni tra classi.

main è un metodo particolare. Il cosiddetto punto di ingresso dell'applicazione, ovvero il primo


metodo che viene richiamato quando si avvia il programma.

Tutti i programmi eseguibili realizzati con C#, compresi quelli per Windows, devono avere una (e
solo una) classe con all'interno un metodo main, altrimenti in fase di esecuzione si otterrà un
messaggio di errore.

L'istruzione System.Console.WriteLine() stampa a video il messaggio, mentre


System.Console.ReadLine() fa sì che l'applicazione aspetti che l'utente prema il tasto INVIO prima
di terminare.

Abbiamo così realizzato un semplice programma in C#, per prendere subito confidenza con i primi
strumenti e per stimolare la nostra curiosità su alcuni concetti che saranno trattati con maggior
dettaglio nelle prossime lezioni.

4.Le classi (1a parte)


Il concetto di classe è alla base di ogni linguaggio di programmazione orientato agli oggetti ed ha la
potenza di definire le caratteristiche di un insieme di oggetti che hanno proprietà e compiono azioni
uguali. Rappresenta tutti gli oggetti che appartengono ad una certa classe, appunto.

Per fare un'esempio si pensi alle automobili: ogni automobile è diversa da un'altra ma tutte hanno
quattro ruote un propulsore e compiono l'azione di avanzare o frenare. Questi elementi comuni le
fanno rientrare in un unico concetto. Un'unica classe.

Più praticamente una classe è una collezione di variabili, metodi e proprietà. Anche in questo caso,
C# riprende la sintassi di Java dove per definire una classe si usa il costrutto class.
public class Persona
{
string mNome;
string mCognome;
//Altre variabili...

public Persona(string Nome, string Cognome)


{
//Imposta le proprietà iniziali della classe.
mNome = Nome;
mCognome = Cognome;
}

public void StampaMessaggio()


{}

private void Appoggio()


{}
}

Una classe è identificata anche come "tipo di dato", infatti porta con se sia la rappresentazione dei
dati, sia le operazioni sugli stessi, in realtà il concetto di classe è più vasto.

Possiamo però cosiderare le istanze di una classe, ovvero gli oggetti che realizzano il concetto di
classe, come variabili definite da un certo tipo di dato.

//Crea un oggetto p che è istanza della classe Persona Persona p = new Persona("Marco",
"Minerva");

Per ogni classe istanziata, viene creato un'oggetto ed è invocato un metodo detto costruttore della
classe. Il costruttore, così come avviene in Java e in C++, ha lo stesso nome della classe e non
restituisce alcun valore (nel nostro esempio il metodo public Persona). In VB .NET, invece, il
costruttore è definito in un metodo dichiarato come Sub New.

Il costruttore è utilizzato per impostare le proprietà che la classe deve avere al momento della sua
creazione: nel nostro esempio, quando si crea una istanza della classe Persona, le sue variabili
mNome e mCognome assumono i valori specificati nel relativo argomento del costruttore.

Dichiarazione dei metodi 

I metodi di una classe possono essere definiti come public, private, protected oppure internal
(Friend in Visual Basic .NET): queste parole chiave vengono solitamente chiamate modificatori di
accesso.

Con public, una routine diventa accessibile da tutte le istanze della classe. Private, invece,
impedisce che la routine sia visibile al di fuori della classe di in cui è definita.

Se non si specifica il modificatore di accesso, private è il valore predefinito. Ad esempio,


utilizzando ancora la nostra classe Persona:
Persona p = new Persona("Marco", "Minerva");

//Corretto, StampaMessaggio è public


p.StampaMessaggio();

//Errato, Appoggio è private, quindi non è accessibile


p.Appoggio();

Dichiarando una metodo della classe Persona come protected, essa sarà visibile nell'ambito in cui
definiamo le classi che ereditano da Persona ma non potrà essere utilizzata nel codice in cui
istanziamo oggetti di questa classe. L'ereditarietà sarà trattata nelle prossime lezioni.

In pratica, protected equivale a private, con la differenza che il metodo è visibile anche alle classi
che ereditano da quella principale. Infine, ciò che è dichiarato come internal è visibile solo
all'interno dell'assembly dell'applicazione; per il concetto di assembly fare riferimento alle Lezioni
dedicate all'uso di Visual Studio 2005.

Dopo il modificatore di accesso del metodo, nella dichiarazione è necessario specificare il tipo di
dato restituito.

È possibile indicare un qualsiasi tipo di dato: int, string, byte, etc. Si può specificare anche il nome
di una classe, poiché, ricordiamo, una classe è un tipo. Questo tipo di metodi corrisponde alle
Function di VB.

Per restituire il valore, è necessario utilizzare la parola chiave return, seguita da una qualsiasi
espressione che abbia lo stesso tipo del valore di ritorno del metodo. Tale istruzione causa anche
l'uscita dal metodo. Ad esempio:

public int Quadrato(int N)


{
return N * N;
//Eventuale codice scritto qui non verrà eseguito.
}

In C# un metodo che restituisce un valore deve obbligatoriamente contenere la parola chiave return,
altrimenti, in fase di compilazione, si otterrà un errore.

Le funzioni di Visual Basic, invece, possono non presentare il return: in tal caso il valore restituito è
quello di default per il particolare tipo di dati.

I metodi che non restituiscono un valore, come StampaMessaggio() e Appoggio() dell'esempio,


hanno tipo void; essi corrispondono alle Sub di Visual Basic. Si può comunque usare la parola
return in un metodo void, che in questo caso ha il solo scopo di uscire dalla procedura: in questo
caso, quindi return non deve essere seguito da alcuna espressione.
5. Le classi (2a parte)
Le proprietà

Una classe può contenere anche un altro tipo di metodi, le cosiddette proprietà. Si tratta di
particolari routine che permettano di leggere e/o impostare i valori di determinate proprietà di una
classe. Aggiungiamo, per esempio, la seguente proprietà alla classe Persona:

public string Nome


{
get { return mNome; }
set { mNome = value; }
}

Essa permette di avere accesso in lettura/scrittura alla variabile mNome, che altrimenti non sarebbe
visibile all'esterno della classe. Il blocco get deve restituire la variabile associata alla proprietà,
quindi, come detto prima, deve contenere l'istruzione return.

Il codice del blocco set, invece, viene eseguito quando si modifica il valore della proprietà; la parola
chiave value contiene il nuovo valore da assegnare. L'utilizzo delle proprietà è intuitivo:

Persona p = new Persona("Marco", "Minerva");

//Stampa "Marco" (richiama il blocco get)


Console.WriteLine(p.Nome);

//Imposta il nome a "Pippo" (richiama il blocco set)


p.Nome = "Pippo";

Allo stesso modo è possibile aggiungere una proprietà Cognome.

È lecito domandarsi perché utilizzare le proprietà, quando si potrebbe più semplicemente rendere
pubbliche le variabili mNome e mCognome. Conviene avere una classe con membri privati
accessibili solo tramite proprietà quando si devono effettuare dei controlli sui valori assegnati ai
membri stessi.

Ad esempio, supponiamo di voler impedire che l'utente inserisca un nome o un cognome vuoto: se
impostiamo le variabili mNome e mCognome come pubbliche questo controllo non può essere
effettuato.

Infatti si ha libero accesso alle variabili che possono essere impostate su qualunque stringa valida
(compresa, dunque, la stringa vuota). Per risolvere il problema, è sufficiente definire i membri come
privati e modificare la proprietà Nome nel modo seguente:

public string Nome


{
get { return mNome; }
set
{
if (value == string.Empty)
mNome = "(Nessun nome)";
else
mNome = value;
}
}

In modo analogo, anche la proprietà Cognome deve essere ridefinita.

string.Empty è un campo a sola lettura che rappresenta la stringa vuota. Se Nome oppure Cognome
vengono impostati sulla stringa vuota, questo codice li definisce, rispettivamente, come Nessun
nome e Nessun cognome.

Notiamo che, per ottenere un comportamento coerente di tutta la classe, anche il costruttore deve
essere modificato in modo da effettuare un controllo sugli argomenti.

È buona norma definire delle proprietà che consentano di modificare i valori delle variabili
utilizzate da una classe, in modo da avere un maggior controllo sui dati specificati, piuttosto che
dare libero accesso alle variabili stesse.

Le proprietà possono anche essere di sola lettura, se dispongono del solo blocco get, oppure di sola
scrittura, se hanno solo il codice associato al set.

Per ottenere un comportamento analogo in Java oppure in C++, è necessario definire due metodi
distinti, i cosiddetti metodi getter (per recuperare il valore) e setter (per impostarlo).

Il codice completo della classe può essere scaricato qui. Nel file da scaricare è incluso un
semplicissimo esempio di uso della classe Persona.

Metodi «statici»

Negli esempi visti finora, per accedere ai metodi della classe Persona occorreva creare un'istanza
della classe. In alcuni casi, tuttavia, può risultare utile avere delle procedure che non hanno bisogno
di un'istanza per essere utilizzate, ma che possono essere richiamate semplicemente con il nome
della classe.

Ad esempio, supponiamo che il metodo Quadrato di cui abbiamo parlato in precedenza sia
contenuto nella classe «Matematica». È sensato pensare che una procedura di questo tipo possa
essere richiamata senza dover prima creare un'istanza della classe che la contiene. Per questo si usa
la parola chiave static (che corrisponde allo Shared di VB .NET):

public static int Quadrato(int N)


{
return N * N;
}

L'utilizzo di questa routine è immediato:

//q assume il valore 25


int q = Matematica.Quadrato(5);
Notiamo che le funzioni WriteLine() e ReadLine() della classe Console, che abbiamo introdotto in
precedenza, sono proprio metodi statici.

I namespace

Più classi possono essere raggruppate in un unico namespace. Ad esempio:

namespace Prova
{
public class Classe1
{}

public class Classe2


{}
}

Con questo costrutto, per dichiarare, ad esempio, un oggetto di tipo Classe1 è necessario scrivere:

Prova.Classe1 c1 = new Prova.Classe1();

lo stesso vale per Classe2. Fatto questo, è possibile accedere ai metodi ed alle proprietà delle classi
nel modo consueto. I namespace sono sempre public.

È possibile definire namespace annidati, ad esempio:

namespace N1
{
namespace N2
{
public class Classe1
{}

public class Classe2


{}
}
}

N1.N2.Classe1 c1 = new N1.N2.Classe1();


N1.N2.Classe2 c2 = new N1.N2.Classe2();

Tutte le classi che compongono l'infrastruttura .NET sono organizzate in namespace. Ad esempio,
la classe Form, da cui ereditano tutte le Windows form (che tratteremo in seguito) è raggiungibile
nel namespace Windows.System.Forms.Form.

Per non dover indicare ogni volta i namespace in cui si trovano le classi che si stanno utilizzando, è
possibile utilizzare la parola chiave using (analoga a import di Java), con cui si specifica in quali
namespace devono essere cercate le classi.

Le clausole using devono essere specificate all'inizio della classe, prima di qualunque altra
istruzione. Ad esempio, nel programma «HelloWorld» realizzato nelle precedenti lezioni si fa uso
della classe Console, che è contenuta nel namespace System; invece di indicarlo esplicitamente, è
possibile ricorrere alla parola chiave using:

using System;

public class HelloWorld


{
public static void Main()
{
Console.WriteLine("Ciao Mondo!");
Console.ReadLine();
}
}

6. I tipi di dati base e passaggio dei parametri


I tipi di dati base derivano direttamente dai tipi di dato di Java, dei quali conservano anche il nome,
anche la conversione tra tipi di dato, il cosiddetto «casting» funziona alla maniera di Java. Ci sono
invece novità introdotte da C# per quanto riguarda i tipi di dato definiti dall'utente: le struct e le
enum.

Il tipo struct permette di definire nuovi tipi di dati a partire da quelli esistenti:

public struct Persona


{
public string Nome;
public string Cognome;
}

Questa dichiarazione definisce un tipo di dato Persona composto da due variabili di tipo stringa (i
cosiddetti membri della struttura). Notiamo che, a differenza del C++, da cui deriva il concetto di
struct, ogni membro deve avere un modificatore di accesso (public, private, ecc.).

Questo perché il tipo struct è molto potente: può essere dotato di un costruttore (che si comporta
esattamente come il costruttore di una classe), di proprietà e di metodi. Modifichiamo la struttura
Persona aggiungendovi un costruttore e una proprietà che restituisce il nome completo, quindi
riprendiamo il nostro primo programma ed aggiorniamolo per mostrare l'uso della struct:

public class HelloWorld


{
public struct Persona
{
public string Nome;
public string Cognome;

public Persona(string Nome, string Cognome)


{
this.Nome = Nome;
this.Cognome = Cognome;
}

public string NomeCompleto


{
get { return Nome + " " + Cognome; }
}
}

public static void Main()


{
Persona p = new Persona("Marco", "Minerva");
System.Console.WriteLine("Ciao " + p.NomeCompleto + "!");
System.Console.ReadLine();
}
}

All'interno del metodo Main la struttura Persona viene utilizzata come se si trattasse di una classe.
Notiamo che, invece di usare il costruttore, avremmo potuto scrivere:

Persona p;
p.Nome = "Marco";
p.Cognome = "Minerva";

Ottenendo lo stesso risultato, ovvero la stampa del messaggio "Ciao Marco Minerva!". Questo è
possibile perché i membri Nome e Cognome della struttura sono stati dichiarati public; se, invece,
fossero stati definiti private, non sarebbe stato consentito utilizzare le tre istruzioni sopra riportate
(si sarebbe ottenuto un errore in fase di compilazione, come già spiegato a proposito delle classi), e
l'unico modo lecito di impostare i membri Nome e Cognome sarebbe stato quello di utilizzare il
costruttore.

Fare clic qui per scaricare l'esempio.

A questo punto è naturale chiedersi quali siano le differenze tra struct e class. La spiegazione è
fornita dalla guida in linea: "Il tipo struct è adatto alla rappresentazione di oggetti leggeri [...] Se ad
esempio si dichiara una matrice di 1.000 oggetti [classi] Point, si allocherà ulteriore memoria per i
riferimenti a ciascun oggetto. In questo caso la struttura risulta meno onerosa". Inoltre una struct
può implementare interfacce ma non può ereditare.

Le enum consentono di associare nomi simbolici a costanti numeriche. Il costrutto deriva dal C++
dove i membri delle enum sono sempre di tipo «int», mentre in C# è possibile indicarne
esplicitamente il tipo:

public enum Stati : byte


{
Italia, America, Francia, Inghilterra //,...
}
Questa dichiarazione definisce una enumerazione i cui membri sono di tipo byte. Richiamando il
metodo ToString() di una variabile di tipo enum, è possibile ottenere la stringa contenente il nome
simbolico del valore corrente. Ad esempio:

Stati s = Stati.America;
System.Console.WriteLine("Stato selezionato: " + s.ToString());

Questo codice stampa a video il messaggio "Stato selezionato: America".

Concludiamo questa Lezione con qualche cenno sul passaggio dei parametri ai metodi. I parametri
in C# possono essere passati in tre modi: in, out e ref.

Queste parole chiave determinano cosa accade alla variabile passata come argomento quando si
esce dal metodo.

• in è il tipo di passaggio predefinito: la routine non può modificare la variabile, o, meglio,


qualunque modifica della variabile interna al metodo viene persa quando si esce dal suo
scope (il tipico passaggio "per valore").
• ref e out sono i classici passaggi di parametro per riferimento. Viene passato il riferimento
(il puntatore) alla variabile e non il suo valore, così anche nello scope del metodo si fa
riferimento allo stasso oggetto in memoria.
La differenza tra ref e out è che ref richiede che si inizializzi la variabile prima della
chiamata del metodo, mentre con out è sufficiente dichiarare la variabile e passarla senza
inizializzazione. In pratica con out è possibile inizializzare intere strutture dati dall'interno di
un metodo.

Vediamo ora alcuni semplici esempi per chiarire quanto illustrato:

// Se non specificato diversamente, il parametro è di tipo «in»


public void Calcola(int N){ N = 10; }

public void CalcolaOut(out int N){ N = 10; }

public void CalcolaRef(ref int N){ N += 1; }

Un possibile utilizzo di questi metodi è il seguente:

int i = 7;
Calcola(i);

//All'uscita, i vale ancora 7

int i = 7;
CalcolaRef(ref i);

//All'uscita, i vale i + 1 = 8

// i è solo dichiarata non inizializzata


int i;

// la passiamo con «out»


CalcolaOut(out i);

//All'uscita, i vale 10

Osserviamo che, per utilizzare un parametro out oppure ref nella chiamata ad un metodo, è
necessario indicare esplicitamente che si tratta di un argomento passato out o ref.

7. Ereditarietà
L'ereditarietà è uno dei concetti base della programmazione orientata agli oggetti. Si definisce
ereditarietà la possibilità per una classe (detta classe derivata) di ereditare da un'altra (la classe base)
le variabili, i metodi, le proprietà e di estendere il concetto della classe base e specializzarlo.

Tornando all'esempio delle automobili potremmo avere come classi derivate da automobili le
sottoclassi: «auto con cambio manuale» e «auto con cambio automatico». Entrambe le sottoclassi
ereditano tutte le proprietà e i comportamenti della classe base ma specializzano il modo di
cambiare le marce.

Tutte le classi del Framework .NET ereditano implicitamente dalla classe base Object. In C# la
relazione di ereditarietà tra le classi si esprime usando i due punti (:) che equivalgono a Inherits
di VB.NET .

Riprendiamo una parte della classe Persona che abbiamo realizzato in precedenza:

public class Persona


{
string mNome;
protected string mCognome;

public Persona(string Nome, string Cognome)


{
mNome = Nome;
mCognome = Cognome;
}

public string Nome


{
get { return mNome; } set { mNome = value; }
}

//...
}

Osserviamo che la variabile mCognome è stata dichiarata come protected. Vogliamo ora definire
un nuovo concetto, quello di Studente, ovvero una Persona dotata di un numero di matricola. Per
fare questo, Studente deve ereditare da Persona (si può dire anche che Studente estende
Persona):

public class Studente : Persona


{
private int mMatricola;
public Studente(string Nome, string Cognome, int Matricola)
: base(Nome, Cognome)
{
mMatricola = Matricola;
}

public int Matricola


{
get { return mMatricola; }
set { mMatricola = value; }
}
}

Avendo definito questa relazione, nella classe Studente è possibile utilizzare tutto ciò che in
Persona è stato definito come public, protected o internal, sia esso una variabile, una routine o
una proprietà.

Ad esempio, all'interno di Studente è possibile utilizzare la variabile mCognome, che in Persona è


definita protected, mentre mNome non è visibile, poiché il modificatore di accesso predefinito è
private.

Esaminiamo il frammento

: base(Nome, Cognome)

subito dopo il costruttore di Studente. Esso richiama il costruttore della classe base passandogli gli
argomenti specificati: in questo modo è possibile impostare le variabili mNome e mCognome della
classe Persona sugli stessi valori passati al costruttore di Studente.

Se avessimo voluto che il nome della persona, indipendentemente da quanto indicato nel costruttore
di Studente, fosse sempre "Pippo", mentre il cognome fosse quello passato, sarebbe stato
sufficiente scrivere:

: base("Pippo", Cognome)

Questo costrutto è necessario perché, quando si crea un oggetto di una classe che deriva da un'altra,
prima del costruttore della classe derivata viene richiamato quello della classe base: nel nostro
esempio, quando si istanzia un oggetto di tipo Studente, viene richiamato prima il costruttore della
classe Persona, e, in seguito, quello di Studente.

Poiché la classe Persona non ha un costruttore senza argomenti, bisogna indicare esplicitamente
tramite la parola chiave «base» quale costruttore richiamare. Se invece Persona avesse avuto un
costruttore privo di argomenti, «base» non sarebbe servita.

Oltre che a questo scopo, «base» serve, in generale, per avere accesso ai metodi, alle variabili ed
alle proprietà della classe che si sta derivando. Il corrispondente di «base» in Visual Basic è la
parola chiave MyBase.

Provando a dichiarare oggetti di tipo Persona e Studente, si ottiene quanto segue:

Studente stud = new Studente("Marco", "", 0);

stud.Matricola = 232440;
// Corretto: Matricola è una property della classe Studente

stud.Cognome = "Minerva";
// Corretto: Cognome è una property ereditata da Persona
Persona pers = new Persona("Marco", "");

pers.Cognome = "Minerva";
// Corretto: Cognome è una property della classe Persona

pers.Matricola = 232440;
// Errato: la property Matricola non è visibile da Persona

Senza le istruzioni : Persona e : base(Nome, Cognome) nella classe Studente, eliminiremmo


l'ereditarietà e otterremmo un messaggio di errore relativo all'istruzione:

stud.Cognome = "Minerva"

Cliccare qui per scaricare le due classi Persona e Studente.

Grazie all'ereditarietà, dove è previsto l'uso di una certa classe, è quasi sempre possibile utilizzare
una classe derivata. Questo è, probabilmente, uno degli aspetti più interessanti dell'ereditarietà.

Per esempio, supponiamo di avere una funzione che, ricevuti come argomenti due oggetti di tipo
Persona, restituisce true se i loro nomi sono uguali:

public static bool NomiUguali(Persona p1, Persona p2)


{
return (p1.Nome == p2.Nome);
}

Come argomenti a questa funzione, possono essere passati sia istanze della classe Persona, sia
istanze di tutte le classi che ereditano da Persona, quindi anche oggetti di tipo Studente. Infatti,
dal momento che esso eredita da Persona, dispone di tutte le proprietà pubbliche della classe base
(in altre parole, uno Studente è una Persona, quindi può essere usato in tutti i contesti in cui è
richiesta una Persona). Il seguente codice è corretto:

Persona pers = new Persona("Marco", "Rossi");


Studente stud = new Studente("Marco", "Minerva", 232440);
Console.WriteLine(NomiUguali(pers, stud)); // Stampa true

Un fatto molto interessante è che all'interno della routine NomiUguali la variabile stud viene vista
come una Persona, e, quindi, di stud sono visibili i metodi e le proprietà pubbliche della classe
Persona, non quelli di Studente.

È possibile scaricare un esempio di utilizzo cliccando qui.

L'ereditarietà è singola, in C# come in VB.NET, ovvero una classe può avere solo una classe base,
a differenza del C++, che supporta l'ereditarietà multipla: in altre parole, non è possibile definire
una classe C che eredita contemporaneamente da A e da B. Torneremo su questi concetti più avanti.
8. Polimorfismo
Il termine polimorfismo indica la possibilità di definire metodi e proprietà con lo stesso nome, in
modo che, ad esempio, una classe derivata possa ridefinire un metodo della classe base con lo stesso
nome.

Continuiamo ad usare le nostre classi Persona e Studente realizzate nelle lezione precedente per
capire il concetto di polimorfismo. Inizialmente, avevamo definito il metodo pubblico
StampaMessaggio() nella classe Persona, ma non ne abbiamo ancora scritto il corpo. Vogliamo fare
in modo che questa routine stampi a video le informazioni sulla persona.

È naturale pensare che, analogamente, anche la classe Studente abbia un metodo


StampaMessaggio() che mostri, oltre alle informazioni sulla persona, anche quelle proprie di uno
studente. Perciò dobbiamo definire i metodi nel modo seguente:

public class Persona


{
//...
public virtual void StampaMessaggio()
{
System.Console.WriteLine("Nome: " + mNome + " " + mCognome);
}
//...
}

public class Studente : Persona


{
//...
public override void StampaMessaggio()
{
System.Console.WriteLine("Nome: " + mNome + " " + mCognome);
System.Console.WriteLine("Matricola: " + mMatricola);
}
//...
}

Il metodo StampaMessaggio() è definito in entrambe le classi, ma ha un comportamento diverso,


dal momento che in Studente viene stampato anche il numero di matricola.

A seconda che si dichiari un oggetto di tipo Persona oppure Studente, verrà richiamata la routine
StampaMessaggio() corrispondente. Per fare questo, abbiamo usato il polimorfismo: il metodo
StampaMessaggio() nella classe base è stato dichiarato usando la parola chiave «virtual» (che
corrisponde a Overridable in Visual Basic .NET): con essa si indica che è possibile avere un'altra
definizione per la stessa routine in una classe derivata.

In Studente, infatti, StampaMessaggio() è stato definito specificando la parola chiave «override»


(Overrides in VB): in questo modo si dice che tale funzione sovrascrive un metodo con lo stesso
nome che è stato definito nella classe base.
In alcuni casi può essere necessario, all'interno del metodo ridefinito, richiamare il metodo che si
sta ridefinendo. Nel nostro esempio, il metodo StampaMessaggio() della classe Studente potrebbe
richiamare il metodo StampaMessaggio() di Persona, che si occupa già di mostrare il nome e il
cognome, limitandosi quindi a stampare solo le informazioni proprie di uno studente.

Per fare questo, si deve usare la parola chiave «base», che, come già accennato nella lezione
precedente, consente di avere accesso alla classe che si sta derivando; alla luce di queste
considerazioni, il metodo StampaMessaggio() di Studente diventa:

public override void StampaMessaggio()


{
base.StampaMessaggio();
System.Console.WriteLine("Matricola: " + mMatricola);
}

In pratica, base.StampaMessaggio() richiama il metodo corrispondente nella classe base.

L'esempio che abbiamo realizzato può essere scaricato cliccando qui.

C'è anche un altro modo per sfruttare il polimorfismo, ovvero usando la parola chiave «new»
(equivalente a Shadows in VB .NET), che nasconde una definizione della classe base con lo stesso
nome. L'esempio sopra riportato, usando «new», diventa:

public class Persona


{
//...
public void StampaMessaggio()
{
System.Console.WriteLine("Nome: " + mNome + " " + mCognome);
}
//...
}

public class Studente : Persona {


//...
public new void StampaMessaggio()
{
base.StampaMessaggio();
System.Console.WriteLine("Matricola: " + mMatricola);
}
//...
}

Come si vede, nella classe base la parola chiave «virtual» è sparita, mentre in Studente il metodo
StampaMessaggio() è preceduto da «new».

È possibile scaricare le due classi modificate cliccando qui.

È importante notare che, usando la coppia «virtual» e «override», il metodo che sovrascrive una
routine della classe base deve avere non solo lo stesso nome, ma anche lo stesso numero di
argomenti e dello stesso tipo.
Invece, usando «new», è possibile ridefinire un metodo completatmente. Con «new» si può
addirittura nascondere una variabile della classe base con un intero metodo nella classe derivata.

Questa rapida panoramica è solo una introduzione al polimorfismo, per approfondire è sempre utile
consultare la Guida in linea.

9. Overloading
Overloading significa «sovraccaricare» ovvero definire più versioni di un metodo, utilizzando lo
stesso nome ma una firma diversa.

Per firma di un metodo si intede il numero e/o il tipo di argomenti nella dichiarazione. Sulla base
degli argomenti effettivamente passati alla routine, verrà riconosciuta la firma e richiamato il
metodo corretto.

Vediamo subito un esempio:

public static void Stampa(string Messaggio)


{
System.Console.WriteLine("Il messaggio è: " + Messaggio);
}

public static void Stampa(int Numero)


{
System.Console.WriteLine("Il numero è: " + Numero);
}

Le due definizioni della routine Stampa() differiscono solo per il tipo dell'argomento: string nel
primo caso, int nel secondo. Richiamando questi due metodi, si ottiene il seguente risultato:

//Stampa: "Il messaggio è: Questa è una stringa".


Stampa("Questo è una stringa");

//Stampa: "Il numero è 15".


Stampa(15);

L'overloading viene spesso usato nella definizione del costruttore di una classe. Riprendiamo il
costruttore della nostra classe Persona:

public Persona(string Nome, string Cognome)


{
if (Nome == string.Empty)
mNome = "(Nessun nome)";
else
mNome = Nome;

if (Cognome == string.Empty)
mCognome = "(Nessun cognone)";
else
mCognome = Cognome;
}

Finora, per creare una persona senza nome né cognome, era necessario passare esplicitamente la
stringa vuota nel costruttore:

Persona p = new Persona("", "");

In questo caso, però, sarebbe preferibile poter istanziare la classe Persona senza passare alcun
argomento al costruttore. Tale risultato può essere facilmente raggiunto con l'overloading del
costruttore:

public Persona()
{
mNome = "Nessun nome";
mCognome = "Nessun cognome";
}

public Persona(string Nome, string Cognome)


{
mNome = Nome;
mCognome = Cognome;
}

Così facendo, se si crea una Persona indicando nome e cognome, essi verranno salvati nelle
variabili mNome e mCognome, poiché viene richiamato il costruttore Persona(string Nome, string
Cognome), mentre, se si crea una Persona senza parametri, è invocato il costruttore Persona(), che
imposta mNome e mCognome, rispettivamente, su "Nessun nome" e "Nessun cognome".

//Imposta: mNome = Marco, mCognome = Minerva


Persona p1 = new Persona("Marco", "Minerva");

//Imposta mNome = Nessun nome, mCognome = Nessun cognome


Persona p2 = new Persona();

Un costruttore può chiamarne un altro. Nel nostro esempio, anziché assegnare esplicitamente le
variabili mNome e mCognome nel costruttore senza argomenti, sarebbe stato possibile richiamare
l'altro costruttore, "dicendogli" come impostare il nome e il cognome della persona:

public Persona() : this("Nessun nome", "Nessun cognome") {}

La parola chiave «this» permette di avere accesso ai metodi, alle variabili ed alle proprietà della
classe corrente, così come «base» permette di avere accesso alla classe da cui si eredita. Il
corrispettivo di «this» in VB.NET è «Me».

Questa soluzione è da preferire poiché, in caso di modifiche al costruttore, è sufficiente intervenire


su un solo punto del codice.
Se, ad esempio, dopo aver creato la classe Persona, si decidesse che il nome deve essere sempre
preceduto dalla stringa "Sig.", nel primo esempio di overloading del costruttore sarebbe stato
necessario modificare il codice in due parti diverse.

Nel caso in cui siano previsti più costruttori, le porzioni di codice da cambiare possono essere più
numerose e, quindi, aumenta la probabilità di dimenticarsi qualche modifica.

La nuova classe Persona è disponibile qui.

Overloading degli operatori

L'overloading degli operatori, derivato dal C++, consiste nel ridefinire il comportamento di
operatori come +, - , <, >, ==, etc., affinchè assumano comportamenti a seconda degli oggetti cui
sono applicati.

C# supporta l'overloading degli operatori a partire dalla sua prima versione, mentre VB.NET ha
introdotto questa funzionalità solo con la versione 2.0 del Framework.

Partiamo come sempre dalla classe Persona. Non avendo altre informazioni, C# assume che due
variabili di tipo Persona siano uguali se fanno riferimento all'istanza dello stesso oggetto, ovvero:

Persona p1 = new Persona("Donald", "Duck");


Persona p2 = new Persona("Donald", "Duck");

//Stampa true
Console.WriteLine(p1 == p1);

//Stampa False, gli oggetti p1 e p2 sono diversi


Console.WriteLine(p1 == p2);

//Ora p2 fa riferimento alla stessa istanza di p1


p2 = p1;

//Stampa true
Console.WriteLine(p1 == p2);

Quello che vorremmo, invece, è che due persone risultassero uguali se il nome e il cognome fossero
gli stessi (nell'esempio sopra riportato, vorremmo che il primo confronto 'p1 == p2' restituisse true).

Per fare questo, o ogni volta facciamo il controllo sulle proprietà Nome e Cognome oppure, più
semplicemente, ridefiniamo l'operatore '==' per la classe Persona creando un metodo con la parola
chiave «operator»:

public static bool operator ==(Persona p1, Persona p2)


{
if (p1.Nome == p2.Nome && p1.Cognome == p2.Cognome)
return true;

return false;
}
public static bool operator !=(Persona p1, Persona p2)
{
if (p1.Nome != p2.Nome || p1.Cognome != p2.Cognome)
return true;

return false;
}

Bisogna ridefinire insieme gli operartori che stabiliscono relazioni simili. Avendo ridefinito
l'operatore '==', il Framework ci richiede che venga ridefinito anche l'operatore '!=' (e viceversa),
così come, se avessimo ridefinito <, avremmo dovuto definire anche > (e viceversa).

L'overloading degli operatori è sempre definito con metodi statici. Dopo l'indicazione del tipo di
dato restituito (solitamente bool oppure lo stesso tipo della classe per cui si sta creando
l'overloading, a seconda dell'operatore in questione), è necessario specificare l'operatore che si sta
ridefinendo.

Sulla guida in linea troviamo l'elenco di tutti gli operatori che possono essere sottoposti ad
overloading.

Infine, bisogna indicare a quali tipi di dati si applica: nel nostro esempio, '==' e '!=' si applicano a
oggetti della classe Persona.

Gli argomenti del metodo sono due nel caso di operatori binari (come mostrato nel nostro
esempio), oppure uno solo se l'operatatore è unario (++, --, ecc.).

Ridefinendo gli operatori '==' e '!=', il compilatore si aspetta che vengano anche ridefiniti i metodi
Equals() e GetHashCode(), che ogni classe possiede in quanto li eredita dalla classe base Object.
Ad ogni modo, se non vengono specificati, si ottiene solo un warning di compilazione. Definiamoli
comunque, per avere una classe più robusta:

public override bool Equals(object o)


{
if (o is Persona)
return this == (Persona) o;

return (object)this == o;
}

public override int GetHashCode()


{
//Questa routine dovrebbe restituire un codice hash identificativo
//dell'istanza corrente della classe.
return 0;
}

Nel metodo Equals() si è utilizzato l'operatore «is», necessario per controllare che l'oggetto passato
alla funzione sia di tipo Persona: in caso positivo, l'oggetto viene convertito e il controllo
sull'uguaglianza è fatto utilizzando la nostra definizione di '=='.
Altrimenti, l'oggetto corrente viene convertito ad Object, quindi si usa il confronto standard, per cui
due variabili di tipo object sono uguali se fanno riferimento all'istanza dello stesso oggetto.

Grazie a questa ridefinizione, ora possiamo ottenere il risultato desiderato:

Persona p1 = new Persona("Donald", "Duck");


Persona p2 = new Persona("Donald", "Duck");
Console.WriteLine(p1 == p2); //Stampa true
Console.WriteLine(p1.Equals(p2)); //Stampa true

Cliccare qui per scaricare la classe Persona realizzata fino a questo punto.

A titolo di esempio, nel file da scaricare è stato ridefinito anche l'operatore '+', che "somma" due
persone, ovvero crea una nuova Persona il cui nome e cognome sono la concatenazione dei nomi e
dei cognomi delle persone di partenza. Al di là dell'utilità di un'operazione del genere, si tratta
comunque di un esempio in più da tenere in considerazione.

10. Le classi astratte


Una classe astratta è un tipo particolare di classe di cui non si può creare una istanza con la parola
chiave «new». Per essere utilizzata, infatti, deve essere obbligatoriamente ereditata. Mostriamo
subito un esempio di classe astratta, partendo dal quale introdurremo una serie di importanti
concetti:

public abstract class Figura


{
protected string mNome;
protected int mNumeroLati;

public Figura(string Nome, int NumeroLati)


{
mNome = Nome;
mNumeroLati = NumeroLati;
}

public string Nome


{
get { return mNome; }
}

public int NumeroLati {


get { return mNumeroLati; }
}

public abstract double Perimetro { get; }


public abstract double Area { get; }
public abstract void Disegna();
}

Una classe astratta si dichiara con la parola chiave «abstract». Al suo interno possono essere
definite variabili, metodi e proprietà, proprio come abbiamo fatto finora (nell'esempio sopra
riportato, le variabili mNome e mNumeroLati, il costruttore, e le proprietà Nome e NumeroLati).

Oltre a ciò, è possibile dichiarare un'altra serie di metodi e proprietà che devono essere
obbligatoriamente implementate dalle classi che ereditano da essa: i cosiddetti metodi e proprietà
astratti.

Nel nostro caso, la classe che eredita da Figura deve fornire una implementazione delle proprietà
Perimetro e Area (dalla definizione si comprende che saranno di sola lettura, se fossero state di
lettura/scrittura avremmo avuto get; set;) e del metodo Disegna.

La classe Quadrato eredita correttamente da Figura:

public class Quadrato : Figura


{
private double mLato;

public Quadrato(int Lato) : base("Quadrato", 4)


{
mLato = Lato;
}

public override double Perimetro


{
get { return mLato * 4; }
}

public override double Area


{
get { return mLato * mLato; }
}

public override void Disegna()


{
//...
}
}

Figura è la classe che racchiude le caratteristiche comuni ad ogni figura geometrica. La definizione
fornita obbliga tutte le classi che ereditano da essa a ridefinire i metodi e le proprietà dichiarati
abstract, altrimenti in fase di compilazione si otterrà un messaggio di errore in quanto alla
dichiarazione del metodo non corrisponde nessuna azione concreta: nessun codice da eseguire.

Ad esempio, eliminando il metodo Disegna dalla classe Quadrato, la compilazione fallisce


segnalando che 'Quadrato' non implementa il metodo astratto ereditato Figura.Disegna().
Per le classi astratte valgono le stesse considerazioni fatte a proposito dell'ereditarietà, ovvero se un
metodo richiede una classe astratta come argomento, al suo posto potrà essere utilizzata una
qualunque classe che eredita da essa.

In VB.NET una classe astratta si dichiara con MustInherit e un metodo astratto con MustOverride.

11. Le interfacce
A differenza delle classi astratte, un'interfaccia è un gruppo completamente astratto di membri
che può essere considerato come la definizione di un contratto: chi implementa una interfaccia si
impegna a scrivere il codice per ciascun metodo.

Questo significa, in primo luogo, che tutti i metodi e le proprietà definite all'interno di un'interfaccia
sono implicitamente astratti, ovvero non è possibile fornirne l'implementazione all'interno
dell'interfaccia stessa. Per creare un'interfaccia, si utilizza la parola chiave «interface»:

public interface IEsempio


{
void Metodo1();
bool Metodo2();

string Proprieta1 { get; set; }


int Proprieta2 { get; }
}

In questo esempio abbiamo seguito la convenzione secondo cui i nomi delle interfacce dovrebbe
iniziare con la lettera I maiuscola. Tutto ciò che è dichiarato all'interno di una interfaccia è
pubblico (ed è un errore indicare il modificatore public).

Un'interfaccia non può contenere variabili, struct, enum e, come già accennato, implementazioni
di metodi (tutte cose che, invece, sono consentite nelle classi astratte).

Un'interfaccia viene implementata da una classe. Per indicare che una classe implementa
un'interfaccia, in C# si usa il carattere ":", lo stesso con cui si esprime l'ereditarietà tra classi (in
VB.NET si usa la parola chiave Implements):

public class Esempio : IEsempio


{
public void Metodo1()
{
//...
}

public bool Metodo2() { return true; }

public string Proprieta1


{
get { return string.Empty; }
set { /*...*/ }
}
public int Proprieta2
{
get { return 0; }
}
}

Come avviene per le classi astratte, la classe che implementa un'interfaccia deve fornire
l'implementazione di tutti i suoi metodi e delle sue proprietà, in questo caso però non c'è bisogno
della parola chiave override, è sufficiente che i metodi definiti abbiano lo stesso nome con cui
sono dichiarati nell'interfaccia.

In VB.NET, invece, i nomi assegnati ai metodi non sono vincolanti, poiché il metodo
dell'interfaccia che si sta implementando è indicato esplicitamente.

Come già visto in precedenza, una classe può ereditare da una sola classe base, mentre può
implementare un numero illimitato di interfacce: in questo caso, è necessario definire i metodi e
le proprietà di tutte le interfacce:

public class Studente : Persona, IEsempio1, IEsempio2

Questa dichiarazione indica che la classe Studente estende Persona e implementa IEsempio1 ed
IEsempio2.

Supponiamo che IEsempio1 ed IEsempio2 contengano un metodo con lo stesso nome, ad esempio
un metodo void Calcola(). La classe Studente deve definirli entrambi: per evitare conflitti, i
nomi dei metodi devono essere preceduti dal nome dall'interfaccia in cui sono definiti:

public void IEsempio1.Calcola() { }

public void IEsempio2.Calcola() { }

Un'interfaccia può ereditarne un'altra: in questo caso, la classe che implementa l'interfaccia
ereditata deve implementare non solo tutti i suoi metodi e proprietà, ma anche quelli dell'interfaccia
base.

Un oggetto che implementa un'interfaccia, inoltre, può essere utilizzato in tutte le parti del codice in
cui è richiesta tale interfaccia, quindi anche le interfacce definiscono una nozione di ereditarietà.

Sia le interfacce sia le classi astratte servono per definire una serie di funzionalità di base che
devono essere realizzate dalle classi che le ereditano o le implementano.

La scelta di utilizzare un'interfaccia oppure una classe astratta può a volte risultare difficile. MSDN
consiglia:

«Mediante le classi astratte possono essere forniti anche membri che sono già stati implementati.
Pertanto, con una classe astratta è possibile garantire una certa quantità di funzionalità identiche,
a differenza di quanto avviene con un'interfaccia (che non può contenere alcuna
implementazione)[...].

Se le funzionalità da creare saranno utilizzate in una vasta gamma di oggetti diversi, usare
un'interfaccia. Le classi astratte devono essere usate principalmente per gli oggetti strettamente
correlati, mentre le interfacce sono più adatte a fornire funzionalità comuni a classi non correlate».
12. Visual Studio 2005
Gli esempi che abbiamo realizzato finora erano di una semplicità tale da poter essere scritti con un
comune editor di testi. Tuttavia, nel momento in cui ci si orienta verso applicazioni più complesse,
soprattutto se dotate di interfaccia grafica, è consigliabile utilizzare un ambiente di sviluppo visuale.

Lo strumento "principe" per la realizzazione di applicazioni che si basano sul Framework .NET è
Visual Studio. Alla sua ultima versione 2005 (in fase di beta testing al momento della stesura di
questa guida), questo ambiente di sviluppo migliora sensibilmente le caratteristiche di usabilità e
potenza che lo hanno da sempre contraddistinto.

Figura 1: L'ambiente di sviluppo Visual Studio 2005

L'interfaccia di Visual Studio 2005 dovrebbe risultare familiare a chi ne ha già utilizzata una
versione precedente. Alcuni riferimenti possiamo trovarli a fine pagina.

La procedura per creare una applicazione Windows è simile alla precendete versione anche se la
finestra di dialogo Nuovo Progetto (New Project) è cambiata, come visibile in figura.
Figura 2: La nuova finestra di dialogo New Project

Le novità introdotte dalla nuova release di Visual Studio, relativamente al miglioramento della
produttività, possono essere riassunte in cinque concetti essenziali:

• Smart Tag: Visual Studio 2005 supporta un discreto numero di Smart Tag per velocizzare
le operazioni più comuni;
• IntelliSense: ora il completamento automatico del codice si attiva non appena si inizia a
digitare qualcosa nell'editor. È attivo anche durante la scrittura di pagine ASP .NET
• Refactoring: con questo termine si intende una serie di funzionalità che permettono di
modificare velocemente e sistematicamente il codice scritto.

tra le altre caratteristiche, cambiando il nome di una variabile, di un metodo o di una


proprietà, uno Smart Tag consente di rinominare automaticamente tutte le occorrenze della
stringa modificata.

Figura 3: Il refactoring di Visual Studio 2005 in azione

• Code Snippets e Surrounding: gli "snippet" sono porzioni di codice di uso comune, come
il codice dei costrutti for e while, che possono essere inseriti facendo clic col tasto destro del
mouse nella finestra del codice e selezionando il comando Insert Snippet..., oppure, più
semplicemente, digitando il nome dello snippet e premendo due volte il tasto TAB.

Queste porzioni di codice possono contenere dei parametri. In tal caso, dopo l'inserimento, il
cursore si posiziona automaticamente sul primo di essi: per spostarsi tra i parametri, si usa il
tasto TAB.

Il Surrounding, invece, è letteralmente la possibilità di "circondare" un blocco di istruzioni


con un certo costrutto, ad esempio for oppure try...catch...finally
Figura 4: Inserimento di uno snippet di codice

• Finestra dei task comuni: selezionando un controllo in una Windows Form, in alto a destra
viene visualizzata un piccolo riquadro con una freccia verso destra: facendo clic su di esso,
compare una finestra che consente di impostare velocemente le proprietà usate più di
frequente per l'oggetto in questione.

Altra novità interessante, forse una delle più richieste, è la funzionalità di Edit & Continue, che
deriva dalle vecchie versioni di Visual Basic e che finalmente è supportata anche da C#.

Consiste nella possibilità di modificare il codice durante il debug del programma senza
interromperne l'esecuzione (ovviamente a patto di non alterare il flusso delle istruzioni).

Ci sono miglioramenti volti a rendere ancora più semplice il debugging delle applicazioni e il Class
Designer (Progettazione Classi), per costruire in modo grafico lo schema delle classi, usando un
approccio in stile UML.

13.Windows Form
La Windows Form, ovvero la finestra dell'applicazione, è il fulcro di ogni applicazione Windows.
La versione 2.0 del framework .NET introduce parecchie novità nella gestione delle Windows
Form, che cercheremo di analizzare nel seguito.

Creiamo una nuova Windows Application (applicazione Windows) con il linguaggio C#, come
indicato nella lezione precedente. Il progetto include di default una Windows Form, contenuta in un
file di nome Form1.cs.

Premiamo il tasto F7 per visualizzare il codice sorgente; in alternativa, è possibile fare clic con il
tasto destro del mouse sul file «Form1.cs» visualizzato nel Solution Explorer (Esplora soluzioni) e
selezionare il comando View code (Visualizza codice). Viene mostrato il codice:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}

Innanzi tutto, notiamo varie clausole using che specificano quali sono i namespace in cui cercare le
classi che verranno utilizzate.

L'insieme di tutte le classi fornite dal Framework .NET prende il nome Base class library. Si tratta
di una raccolta vastissima, ed è impossibile anche solo citare tutte le classi che contiene.

Viene poi definito il namespace WindowsApplication1. Tutte le Windows Form create con Visual
Studio, così come le classi, le interfacce, gli user control, etc., sono automaticamente inserite
all'interno di un namespace, che per impostazione predefinita ha lo stesso nome del progetto.

Questa impostazione può essere cambiata facendo clic con il pulsante destro del mouse sul nome
del progetto all'interno del Solution Explorer e selezionando il comando Properties (Proprietà).

VB.NET, al contrario, di default non include le classi create all'interno di namespace.

Il codice evidenzia che anche una Windows Form è una classe, che eredita dalla classe «Form».

Osserviamo la parola chiave partial, una novità della versione 2.0 del Framework che permette di
definire classi "parziali".

Di norma, ogni classe risiede in un file separato, ma utilizzando partial è possibile creare classi
definite su più file. Da ciascun file parziale è possibile accedere alle variabili, ai metodi, alle
proprietà, di tutti gli altri file parziali relativi alla stessa classe, proprio come se l'intera
dichiarazione della classe fosse contenuta in un unico file.

Questo nuovo concetto favorisce una maggiore pulizia e leggibilità del codice della Windows Form:
il codice di progettazione, che imposta le proprietà della finestra e dei controlli in essa inseriti, in
precedenza veniva salvato nello stesso file del form, all'interno della region Windows Form
Designer generated code (Codice generato da Progrettazione Windows Form), mentre ora è
memorizzato in un file di supporto gestito internamente dall'ambiente di sviluppo e chiamato
Form1.Designer.cs (al posto di Form1 c'è il nome del form).

Vedremo che questo concetto somiglia molto a quello di code behind nelle applicazioni Web.

L'unico indizio dell'esistenza di questo file è il metodo InitializeComponent() che viene


richiamato nel costruttore del form: esso, come nelle precedenti versioni, ha il compito di
inizializzare tutti gli oggetti contenuti nel form.

Per visualizzare il contenuto del file Form1.Designer.cs, premere il pulsante + posto a fianco del
nome del form nel Solution Explorer.
In VB.NET, invece, il i file del Deesigner di default
d è nasscosto. Per visualizzarl
v o, è necessaario fare
clic sul pullsante Show
w All Files all'interno
a d Solution Explorer. Inoltre, in V
del VB la Windo ows Form
non è dichiiarata comee «Partial» (anche
( se, inn realtà, ancch'essa è unaa classe parrziale).

Figura 1. Ill Solution Explorer


E

Le classi parziali
p e il loro
l utilizzoo nelle Winddows Form potrebbero sembrare nnovità di poco conto
che, però, si
s fanno appprezzare quando si creaa una finestrra con un numero elevvato di conttrolli.

Nelle preceedenti versiioni il file di


d definizionne della Win ndows Form m si reimpivva di una graan quantità
di codice mantenuto
m d
dall'ambient te di svilupppo (che, quindi, l'utentee non avrebbbe dovuto modificare,
m
come indiccato chiarammente dai coommenti), a discapito dellad leggibiilità.

Visual Studio 2005 coonsente un posizionam


p mento dei co ontrolli sullla Windowss Form moltto più
agevole chhe in passatoo: è sparita la
l griglia chhe ricoprivaa il form in fase
f di proggettazione, poiché
p ora i
controlli seembrano "caalamitati" dai
d bordi dellla finestra e dagli altri oggetti; inooltre, durantte le
operazioni di trascinammento, venggono visuallizzate dellee linee guida per conseentire un
posizionammento più prreciso deglii elementi nella
n finestraa.

14. La
L gesttione degli eventti
La gestion
ne degli eventi nel Frammework .NE ET 2.0 e in Visual
V Studdio 2005 è rimasta pressoché
immutata rispetto
r alle versioni prrecedenti.

Continuiammo a lavorarre con la Windows


W Forrm realizzatta nella scorrsa lezione, precisando che
quanto direemo in seguuito a propoosito degli evventi vale per
p ogni tippo di controollo, sia esso
o una form,
un pulsantee, una casellla di testo, un
u comandoo di menu, etc.
e

Possiamo definire
d un evento com me il verificaarsi di una condizionee: dalla presssione di un n pulsante,
alla digitazzione in unaa casella di testo.
t Quando si verificca una di quueste condizzioni, diciam
mo che il
controllo genera
g (oppuure lancia) un
u evento. Ad
A esempio o, quando sii preme il puulsante sinisstro del
mouse su di d un bottonne, esso gennera l'eventoo «Click».

Ad ogni evvento può esssere associiata una azioone descrittta da una paarticolare rooutine che viiene
eseguita oggni volta chhe l'evento si verifica.
Supponiamo di voler eseguire una certa azione in fase di caricamento del form. Occorre anzitutto
creare un gestore di eventi (detto Event Handler) per l'evento «Load» della finestra Form1.
Aggiungiamo il seguente codice nel costruttore della classe:

this.Load += new EventHandler(Form1_Load);

Per installare un event handler, occorre specificare il nome dell'oggetto che espone l'evento: in
questo caso abbiamo usato this, che, come abbiamo già accennato, rappresenta la classe corrente (in
questo caso il form).

Di seguito, dopo il punto, si indica il nome dell'evento da gestire: gli eventi disponibili sono
riconoscibili, nell'elenco visualizzato tramite l'IntelliSense, dal simbolo di un fulmine.

Figura 1: Gli eventi del form

L' operatore '+=' indica che si sta aggiungendo un gestore per l'evento. Si usa '+=', e non solo '=',
perché è possibile specificare più handler per lo stesso evento.

L'IntelliSense ci viene ancora una volta in aiuto, proponendoci un nome per il gestore degli eventi: è
sufficiente premere il tasto TAB per confermare il suggerimento.

Il metodo che rappresenta l'azione collegata all'evento viene detto "delegato" (delegate). Infatti
viene delegata a quel metodo la gestione dell'evento. Form1_Load() in questo caso è un delegato.

Questo metodo di creazione degli event handler corrisponde ad «AddHandler» in VB.NET

EventHandler e tutte le classi derivate sono gestori di eventi. Ogni oggetto istanza di EventHanlder
richiede il riferimento a un delegato per la gestione di un evento, la stringa "Form1_Load" senza
parentesi altro non è che un riferimento (puntatore) al metodo Form1_Load().

Un delegato in astratto ha però una certa firma e per rimpiazzarlo con un altro metodo come
Form_Load() occorre che questo abbia i medesimi argomenti in tipo e numero e lo stesso tipo di
ritorno.
Con EventHandler(Form1_Load) si esprime il fatto che il metodo Form1_Load() avrà argomenti
uguali in numero e tipo a quelli indicati nella dichiarazione del delegate EventHandler e avrà come
tipo di ritorno il medesimo tipo del delegate.

Un'altra pressione del tasto TAB aggiunge automaticamente, all'interno della classe, il metodo che
sarà richiamato quando si genera l'evento in questione:

void Form1_Load(object sender, EventArgs e)


{
throw new Exception("Metodo non implementato.");
}

L'istruzione «throw new Exception()» solleva una eccezione. Ci occuperemo delle eccezioni in
seguito.

Gli argomenti di questo metodo sono due:

• Object sender rappresenta l'oggetto (il controllo, la classe) che ha generato l'evento, ed è
utile nel caso in cui la stessa routine venga eseguita in risposta ad eventi lanciati da oggetti
diversi, per sapere chi effettivamente lo ha generato;
• EventAgrs e. EventArgs, come pure tutte le classi da essa derivate, contiene gli argomenti
associati all'evento. Nel nostro caso «e» è di tipo EventArgs, ma, può essere specializzata a
seconda dell'evento.
Ad esempio, nel caso di eventi di gestione del mouse (MouseClick,MouseMove, etc.), è di
tipo «MouseEventArgs» e permette di conoscere la posizione del cursore o quale tasto del
mouse è stato premuto.

Proviamo ad utilizzare la routine Form1_Load. Vogliamo fare in modo che, all'avvio del
programma, venga visualizzata una MessageBox con la scritta "Ciao Mondo!". A tal scopo, è
sufficiente definire il metodo Form1_Load nel modo seguente:

void Form1_Load(object sender, EventArgs e)


{
MessageBox.Show(
"Ciao Mondo!", "Evento",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}

MessageBox è una classe che contiene un metodo statico, Show() che visualizza una finestra di
messaggio. Esso dispone di parecchie definizioni tramite overloading (21 per la precisione); la
versione più utilizzata, anche da noi, prende come argomenti il messaggio da visualizzare, il titolo
della MessageBox, i pulsanti e l'icona della finestra.

Eseguiamo ora l'applicazione facendo clic sul pulsante della barra degli strumenti (oppure
premendo il tasto F5). Se non sono stati commessi errori, dopo la compilazione dovrebbe comparire
la finestra riportata in figura.
Figura 2: L'output del programma

Gli event handler possono essere creati anche in un altro modo. All'interno della finestra di
progettazione, selezionare il controllo per cui si vuole definire il gestore, quindi spostarsi nella
finestra Properties (Proprietà) e fare clic sull'icona raffigurante un fulmine.

Comparirà un elenco contenente tutti gli eventi generati dal controllo in questione. Selezionandone
uno, nella parte bassa della finestra sarà visualizzato un breve messaggio che indica quando tale
evento viene lanciato.

Clicchiamo su un evento, digitiamo il nome di una routine nella casella di testo corrispondente e
premiamo il tasto INVIO, Visual Studio aggiungerà automaticamente il codice per installare l'event
handler (all'interno del file del Designer) ed inserirà nel file del form una routine con il nome
indicato.

In alternativa, facendo doppio clic sul nome dell'evento, sarà creato un event handler e una routine
di gestione con il nome predefinito (formato da nome del controllo, trattino basso e nome
dell'evento).

Per eliminare un gestore degli eventi creato in questo modo, fare clic con il tasto destro del mouse
sull'evento e selezionare il comando Reset (Annulla). Infine, per tornare alla visualizzazione delle
proprietà del controllo, premere il pulsante Properties (Proprietà) che si trova a sinistra dell'icona
con il fulmine.

Figura 3: La finestra per la definizione degli event handler

Ancora, facendo doppio clic su di un oggetto, Visual Studio definirà automaticamente un event
handler per l'evento predefinito dell'oggetto in questione (Load per i form, Click per i pulsanti,
TextChanged per le caselle di testo, ecc.).
L'esempio che abbiamo realizzato può essere scaricato cliccando qui.

Questa rapida panoramica sulla Windows Form ha lo scopo di fornire le conoscenze di base che
permettano di approfondire l'argomento senza troppa fatica. Per una discussione più dettagliata, si
rimanda ai riferimenti a fondo pagina alla guida a VB.NET: nonostante il linguaggio sia differente, i
concetti esposti risultano validi anche per C#.

15. Controlli Windows


Il framework .NET mette a disposizione una nutrita serie di oggetti che possono essere inseriti in
una Windows Form , i cosiddetti Windows Controls (Controlli Windows o, più semplicemente,
Controlli).

Sono gli oggetti più comuni a tutte le interfacce grafiche come bottoni, checkbox, etc., ma troviamo
anche controlli per la gestione di dati o per il dialogo con le periferiche.

Tutti impacchettati in classi con metodi e proprietà che ne permettono la personalizzazione a


seconda delle esigenze, i controlli consentonto di realizzare applicazioni dall'aspetto professionale
con uno sforzo minimo.

Dato il loro numero (più di 60), è impossibile analizzarli tutti in questa sede: ci limiteremo a
mostrare quali sono le novità dei principali controlli del Framework 2.0, rimandando alla guida su
VB.NET (ed alla guida in linea di Visual Studio) per maggiori dettagli sugli altri oggetti.

All'interno della Casella degli strumenti di Visual Studio 2005 (Toolbox in inglese), i controlli sono
divisi in categorie:

• All Windows Forms: che visualizza in un'unica scheda tutti i controlli disponibili;
• Common Controls, che contiene gli oggetti tipicamente presenti in ogni finestra (Label,
TextBox, Button, ListBox, ComboBox, etc.);
• Containers, che raggruppa i controlli che permettono di gestire il layout del form, ovvero la
disposizione degli oggetti;
• menù & Toolbars, contenente oggetti che permettono di aggiungere barre dei menù, barre
degli strumenti, barra di stato e menù contestuali all'applicazione;
• Data, che contiene gli strumenti che permettono di lavorare con fonti di dati esterne (come i
database);
• Components, che raggruppa oggetti i quali consentono di interagire con il sistema operativo
(per gestire le Active Directory ed il registro eventi di Windows, monitorare le modifiche al
file system, etc.);
• Printing, che contiene gli oggetti necessari per aggiungere le funzionalità di stampa
all'applicazione;
• Dialogs, contenente i controlli che consentono di visualizzare le finestre di dialogo comuni
di Windows, come Apri e Salva con nome.
Figura 1. Toolbox di Visual Studio 2005

Controlli come TextBox, ListBox e PictureBox sono stati aggiornati con l'aggiunta di alcune
proprietà e funzioni, ma il loro utilizzo è rimasto essenzialmente uguale rispetto alle versioni
precedenti di .NET .

I controlli MenuStrip (barra dei menù) e ToolStrip (barra degli strumenti), sono stati notevolmente
potenziati e supportano lo stile introdotto con Office 2003:

Figura 2. Nuovo stile per barra menù e strumenti

Essi sostituiscono i vecchi controlli MainMenu e ToolBar. Al loro interno è ora possibile inserire
un numero maggiori di elementi: un menù, infatti, può contenere anche TextBox e ComboBox, così
come la barra degli strumenti, che è in grado di ospitare anche ProgressBar. Il MenuStrip, inoltre,
consente di associare icone alle voci di menù.

Anche il controllo StatusStrip, sostituto del vecchio StatusBar ed utilizzato per visualizzare una
barra di stato nell'applicazione, ha subito le stesse operazioni di aggiornamento.

Un'altra novità è rappresentata dai controlli per gestire il layout della finestra, contenuti nella
sezione Containers della «Casella degli strumenti». Accanto ai controlli già presenti nelle versioni
del Framework, infatti, sono stati inseriti oggetti come FlowLayoutPanel e tableLayoutPanel, che
consentono di disporre automaticamente i controlli inseriti al loro interno.

Mostriamo ora un semplice esempio d'uso di alcuni controlli realizzando una piccola applicazione
per Windows: un visualizzatore di immagini minimale, composto da una casella di testo in cui
digitare il percorso del file, un pulsante per visualizzare la finestra di dialogo «Apri» e un pulsante
che mostra l'immagine selezionata in una PictureBox.

Una CheckBox permetterà di stabilire se si vuole visualizzare l'immagine nelle sue dimensioni reali
oppure ingrandita a occupare tutta l'area disponibile.
Figura 3. Interfaccia della applicazione esempio

Notiamo che è stato inserito anche un controllo di tipo OpenFileDialog. Ora impostiamo le
proprietà dei vari oggetti come indicato nella tabella seguente:

Controllo (Classe) Proprietà Valore


frmVisualizzatore AcceptButton btnVisualizza
(Form) StartPosition CenterScreen
Text Visualizzatore
Immagini
lblNomeFile (Label) Text &amp;Nome file:
txtNomeFile
(TextBox)
btnSfoglia (Button) Text ...
btnVisualizza Text &amp;Visualizza
(Button)
picImmagine borderStyle Fixed3D
(PictureBox)
chkAdattaImmagine Text &amp;Adatta
(CheckBox) automaticamente
immagine alla finestra
ofdApri FileName (Stringa vuota)
(OpenFileDialog) Filter Tutti i file immagine
(*.bmp; *.jpg; *.gif;
*.png)
Title Seleziona un file
immagine

Cliccare qui per scaricare il progetto.

Non ci resta che scrivere il codice da associare agli eventi dei controlli. Anziutto vogliamo che,
cliccando sul pulsante btnSfoglia, si apra la finestra di esplorazione per selezionare un'immagine.

Per associare automaticamente un Event Handler all'evento principale di un determinato


oggetto, è sufficiente un doppio click sull'oggetto in questione. Nel nostro caso definiamo l'event
handler per l'evento «Click» del pulsante. Nella finestra del codice appare la definizione del metodo
delegato e possiamo aggiungere il codice di gestione dell'evento:

private void btnSfoglia_Click(object sender, EventArgs e)


{
if (ofdApri.ShowDialog() == DialogResult.OK)
txtNomeFile.Text = ofdApri.FileName;
}

Il metodo ofdApri.ShowDialog() visualizza la finestra di dialogo per la selezione di un file; il tipo


del file è indicato nella proprietà Filter del controllo.

La routine restituisce un valore di tipo DialogResult che permette di conoscere il motivo della
chiusura della finestra: DialogResult.OK indica che la finestra è stata chiusa perché l'utente ha
premuto il pulsante «OK» (se viene premuto «Annulla», il valore restituito è DialogResult.Cancel).

Il nome del file selezionato è salvato nella proprietà FileName del controllo.

Ora dobbiamo visualizzare l'immagine selezionata. Facciamo doppio clic sul pulsante
«bntVisualizza» e scriviamo questa semplice istruzione:

private void btnVisualizza_Click(object sender, EventArgs e)


{
picImmagine.Image = Bitmap.FromFile(txtNomeFile.Text);
}

Per caricare l'immagine, abbiamo usato il metodo statico FromFile() della classe Bitmap,
contenuta nel namespace System.Drawing, il quale prende come argomento il nome di un file e
restituisce un oggetto che rappresenta l'immagine in esso contenuta. A questo punto, è sufficiente
assegnarlo alla proprietà Image del controllo picImmagine perché venga visualizzata.

Ci resta solo da definire il comportamento dell'applicazione sul click del pulsante con
chkAdattaImmagine spuntata:

private void chkAdattaImmagine_CheckedChanged(object sender, EventArgs e)


{
if (chkAdattaImmagine.Checked == true)
picImmagine.sizeMode = PictureBoxsizeMode.StretchImage;
else
picImmagine.sizeMode = PictureBoxsizeMode.Normal;
}

CheckedChanged è l'evento che viene generato quando si fa clic sulla checkBox.

Queste istruzioni fanno sì che, quando la casella è selezionata, ovvero la proprietà «Checked» vale
true, la proprietà «sizeMode» della PictureBox venga imposta su
PictureBoxsizeMode.StretchImage, in modo che l'immagine si adatta all'area disponibile.

La nostra applicazione è pronta. Possiamo eseguirla premendo il tasto F5 per verificare che il suo
comportamento sia quello desiderato.
L'esempio completo è disponibile per il download cliccando qui.

A causa di un bug della versione Beta 2 di Visual Studio 2005 e del Framework .NET 2.0,
cambiando la proprietà sizeMode a tempo di esecuzione, l'altezza del controllo PictureBox
diminuisce; tale comportamento può essere ignorato, poiché verrà sicuramente risolto con la
versione definitiva del Framework .NET 2.0, e comunque è facilmente aggirabile facendo in modo
che, ogni volta che si cambia la proprietà sizeMode, la dimensione del controllo venga reimpostata
sul valore iniziale.

16. Le eccezioni
Fino a questo punto del corso non abbiamo mai parlato della gestione degli errori in C#.
Semplificando, un errore è il verificarsi di una situazione imprevista durante l'esecuzione di un
programma.

Gli errori nel mondo .NET, nella migliore tradizione Object Oriented (Java, C++, etc.) vengono
rappresentati con le eccezioni: una intera gerarchia di classi per la gestione degli eventi indesiderati.

Le eccezioni vengono "sollevate" al verificarsi di una situazione anomala, come un errore. Si


possono ignorare, con il rischio di avere delle brutte sorprese a run-time, oppure si possono
intercettare e gestire.

Questo implica l'uso di un costrutto nato appositamente per l'intercettazione delle eccezioni: try-
catch-finally.

try {
//Codice che potrebbe sollevare una eccezione.
} catch {
//Codice da eseguire in caso di eccezione.
} finally {
//Codice da eseguire in ogni caso
}

Se il codice che viene eseguito all'interno del blocco try solleva una eccezione, questa viene
catturata e viene eseguito il blocco catch. In ogni caso viene eseguito poi anche il blocco finally.

Nel costrutto try-catch è necessario specificare almeno una clausola catch oppure una finally; si
possono indicare più catch, una per ogni tipo di errore che si vuole gestire, mentre è consentito
inserire solo una clausola finally.

Nella clausola catch si può indicare il tipo di eccezione che si vuole gestire; ad esempio:

catch (OverflowException ex)


{
//Codice da eseguire in caso di errore di overflow.
}
catch (DivideByZeroException ex)
{
//Codice da eseguire in caso di errore dovuto
//ad una divisione per 0.
}

Il primo blocco catch viene eseguito se si verifica un errore di overflow, ad esempio quando si cerca
di assegnare ad una variabile un valore al di fuori del suo range di definizione. Il secondo viene
invocato se si effettua una divisione per 0.

In entrambi i casi, l'oggetto «ex» contiene varie informazioni sull'errore. Ad esempio, la proprietà
ex.Message restituisce una stringa di descrizione della causa dell'eccezione.

Aggiorniamo ora l'applicazione che abbiamo realizzato nella lezione precedente aggiungendo la
gestione degli errori. In particolare, consideriamo l'istruzione

picImmagine.Image = Bitmap.FromFile(txtNomeFile.Text);

Nel caso in cui il file specificato non esista, il metodo FromFile lancia un'eccezione di tipo
FileNotFoundException. Inseriamo, quindi, tale istruzione in un blocco try...catch:

try {
picImmagine.Image = Bitmap.FromFile(txtNomeFile.Text);
} catch (FileNotFoundException){
MessageBox.Show("Impossibile trovare il file " + txtNomeFile.Text + ".", "Errore",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}

Dal momento che anche le eccezioni sono classi, anch'esse sono raggruppate all'interno di
namespace. Per esempio, perché il Framework sappia dove cercare la classe
FileNotFoundException, tra le clausole «using» va aggiunta quella relativa al namespace
«System.IO».

Ogni volta che, cercando di caricare un file, si verifica un'eccezione di tipo


«FileNotFoundException», viene eseguito il blocco catch corrispondente, il quale visualizza una
finestra di messaggio. L'errore di file non trovato, però, non è l'unico che può capitare quando si
cerca di aprire un file: ad esempio, se la memoria a disposizione non è sufficiente per completare il
caricamento, si ottiene un errore di tipo OutOfMemoryException.

Ancora, se si preme il pulsante «Visualizza» senza aver digitato alcun nome di file, si verifica una
ArgumentException.

Aggiungiamo, quindi, un secondo blocco catch per trattare tutti gli altri casi, ottenendo
complessivamente:

try
{
picImmagine.Image = Bitmap.FromFile(txtNomeFile.Text);
}

catch (FileNotFoundException)
{
MessageBox.Show(
"Impossibile trovare il file " + txtNomeFile.Text + ".",
"Errore",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}

catch (Exception ex) {


MessageBox.Show(
"Errore durante il caricamento: " + ex.Message,
"Errore",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}

Poiché Exception è la classe base da cui derivano tutte le altre eccezioni, il secondo blocco catch è
eseguito ogni volta che il metodo FromFile lancia un'eccezione che non sia di tipo
«FileNotFoundException» (e che non derivi da essa).

In questo caso abbiamo usato l'informazione contenuta nell'eccezione «ex» per sapere con esattezza
l'origine dell'errore.

L'esempio completo è disponibile qui.

È sempre meglio evitare la gestione delle eccezioni, cercando di prevedere, dove possibile, le cause
che possono portare ad una situazione imprevista: un abuso di try...catch, infatti, può appesantire
molto un programma. Nel nostra applicazione, ad esempio, l'errore ArgumentException può sempre
essere evitato semplicemente facendo in modo che il metodo FromFile venga richiamato solo se è
stato digitato qualcosa nella casella di testo.

17. Programmazione web: C# e ASP .NET


Il volto Web del Framework .NET è meglio noto come ASP .NET. A dispetto del nome ASP .NET
ha ben poco a che fare con il predecessore ASP: esso riesce a portare la potenza della
programmazione ad oggetti tipica di C# e di VB.NET anche nella realizzazione di applicazioni
Web.

I mondi delle applicazioni desktop e delle applicazioni web convergono con .NET. È stato
introdotto un modello di programmazione per il Web che ha permesso ai programmatori VB e C# di
trasferire le proprie competenze di sviluppo Windows in ambito Web e viceversa.

Per realizzare ed eseguire un'applicazione Web con ASP .NET è necessario che nel sistema sia
installato il framework .NET ed un web server in grado di interpretare ed eseguire i file ASP .NET,
che hanno estensione .aspx.

Visual Studio 2005 include un tool di sviluppo Web completamente rinnovato, Visual Web
Developer, che integra un piccolo server Web per consentire a tutti di sperimentare da subito le
potenzialità di ASP .NET.
ASP.NET fornisce un ambiente orientato agli oggetti e basato sugli eventi per le applicazioni Web.
Le pagine non sono altro che classi C# o VB.NET con le caratteristiche che abbiamo già introdotto.
Esse ereditano le funzionalità di base dalla classe «Page» come una Windows Form eredita dalla
classe «Form».

Realizziamo una prima applicazione Web con Visual Studio 2005. Ne approfitteremo per introdurre
i primi concetti relativi alla gestione delle pagine ASP.NET . Dopo aver avviato l'ambiente di
sviluppo, selezioniamo dal menù File>new e clicchiamo sulla voce «Web Site» (Sito Web). Appare
una finestra di dialogo:

Figura 1. La finestra di dialogo New Web Site

Scegliamo «ASP .NET Web Site», quindi, nella parte bassa della finestra, indichiamo che la
locazione del sito è il file system (le altre opzioni sono HTTP e FTP) e che il linguaggio utilizzato è
C#. clicchiamo su «OK» e Visual Studio crea una soluzione contenente un file di nome
«Default.aspx» (la pagina iniziale del sito) e uno contenente il cosiddetto code behind della pagina,
chiamato «Default.aspx.cs».

Questa caratteristica è molto importante: tutto il codice degli script della pagina, che in genere è
"immerso" all'interno dell'HTML, ora può risiedere anche in un file separato, consentendo una
separazione tra grafica e codice.

Figura 2. Il Solution Explorer di Visual Web Developer

Cliccando con il pulsante destro su «Default.aspx» e selezionando «View Designer» (Visualizza


Progettazione) si apre l'editor visuale della pagina Web.

La casella degli strumenti sulla sinistra ospita un gran numero di controlli divisi in varie categorie.
Per il momento ci interessano quelli che si trovano nella categoria Standard, la quale contiene una
serie di controlli simili a quelli per le applicazioni desktop.
Figura 3. La toolbox di Visual Web Developer

Con un doppio clic sul controllo «Label», inseriamo nella pagina un'etichetta; sulla destra, nella
finestra «Properties», saranno visualizzate le proprietà dell'oggetto, le modifichiamo come
indicato di seguito (la proprietà ID corrisponde alla proprietà Name dei controlli Windows):

Proprietà Valore
lblOraConnessione btnVisualizza
Text Ora di connessione:

Proviamo ad eseguire l'applicazione ASP .NET finora realizzata. Il progetto si avvia come se si
trattasse di un'applicazione Windows, quindi premendo il tasto F5 oppure il pulsante sulla barra
degli strumenti.

La prima volta che l'applicazione viene lanciata comparirà una finestra che chiede se si vuole
attivare il debug per il sito Web, selezioniamo l'opzione Add new Web.config file with debugging
enabled e confermiamo con «OK»: fatto ciò, verrà avviato il server Web integrato in Visual Web
Developer e l'applicazione ASP .NET sarà visualizzata all'interno del browser Web predefinito.

Nel caso in cui abbiamo un progetto composto da più pagine, per scegliere quale mostrare nel
browser è sufficiente selezionarla nel Solution Explorer prima di avviare l'esecuzione (non è
necessario impostarla come pagina di avvio, come si doveva fare con le precedenti versioni di
Visual Studio).

Per il momento, l'unica cosa che appare nella pagina è la stringa "Ora di connessione", come si
trattasse di una pagina statica.

Chiudiamo il browser e torniamo in Visual Studio per aggiungere una prima componente dinamica.
Con un doppio clic su un punto vuoto della pagina all'interno dell'editor aggingiamo
automaticamente il gestore dell'evento Load, che viene inserito nel "code behind" della pagina
ovvero nel file «Default.aspx.cs».

protected void Page_Load(object sender, EventArgs e)


{

La firma dell'evento è esattamente la stessa dell'evento Load di una Windows Form. Anche il
significato è lo stesso: il codice inserito in questo metodo verrà eseguito ogni volta che la pagina
sarà caricata. Vogliamo fare in modo che, ogni volta che si apre o si aggiorna la pagina, l'etichetta
definita in precedenza contenga la data e l'ora dell'operazione. Scriviamo:
protected void Page_Load(object sender, EventArgs e)
{
lblOraConnessione.Text = "Ora di connessione: " + DateTime.Now;
}

Il codice della pagina «Default.cs» non ha bisogno di modifiche. Proviamo ora ad eseguire
l'applicazione. Se non sono stati commessi errori, nel browser Web dovrebbe apparire qualcosa di
simile a:

Figura 4. Esecuzione del programma

18. Web form


Così come la Windows Form è il componente base di ogni applicazione per Windows, la Web
Form è il centro di un'applicazione ASP .NET.

Con Visual Web Developer è possibile costruire graficamente una Web Form proprio come si fa
con la Windows Form, utilizzando controlli simili per aspetto e proprietà: è poi l'editor che, in
modo del tutto automatico, si preoccupa di produrre "dietro le quinte" il codice HTML necessario
per visualizzare gli elementi dell'interfaccia all'interno del browser Web.

Scendendo nei dettagli dell'implementazione, una Web Form è una pagina Web in cui tutti gli
elementi (testo, elenchi, pulsanti, caselle, ecc.) sono inseriti all'interno di una normale form HTML,
ovvero tra i tag <form> e </form>.

Per leggere il codice HTML è sufficiente fare clic sul pulsante «Source» (Sorgente) visualizzato
nella parte bassa del Designer.

Figura 1. Modalità di visualizzazione di una pagina ASP .NET

Anche la Web Form è un oggetto al pari di tutti gli elementi in essa contenuti, per cui è possibile
accedere a metodi e proprietà. Inoltre dal codice di una pagina è possibile accedere all'intera libreria
di classi di .NET . Abbiamo già visto un esempio di come utilizzare la classe DateTime per
recuperare la data e l'ora di sistema.

PostBack
Quando la pagina viene caricata all'interno del browser, viene generato l'evento Page_Load. Nel
codice del delegato per Page_Load è possibile verificare se l'utente sta effettuando il primo accesso
alla pagina oppure sta effettuando un PostBack.

La tecnica del postback è quella che consente ad una pagina Web di trasmettere a se stessa delle
informazioni di stato.

In pratica in una pagina web dinamica viene creato un form che ha come campo «action» l'indirizzo
della pagina stessa ed al suo interno dei campi «hidden» che mantengono informazioni sullo
stato della pagina. Per esempio potremmo avere un campo che contiene il testo di una casella di
testo. Molto spesso vengono usati campi «hidden» per ricordare se includere una porzione di codice
o meno.

Queste informazioni una volta generato un evento "Submit" (che non necessariamente scaturisce da
un bottone), vengono "rispedite indietro" alla pagina che le usa per modificare il suo aspetto.

Questa tecnica è abbastanza comune per chi programma in ASP o PHP, ma ASP.NET l'ha intergata
nel concetto di Web Form con il PostBack e il ViewState che vedremo tra breve

La proprietà IsPostBack restituisce il valore true quando il modulo presente nella pagina è stato
eseguito, false quando si tratta del primo accesso alla pagina:

protected void Page_Load(object sender, EventArgs e)


{
if (Page.IsPostBack)
{
//Il form è stato già processato.
//È stato sollevato qualche evento che
//ha provocato un PostBack
}
else
{
//Primo caricamento della pagina.
}
}

Esempio

Realizziamo ora una piccola applicazione Web. Vogliamo fare in modo che l'utente possa digitare il
suo nome e, premendo un pulsante, riceva un messaggio di saluto.

Usiamo i controlli della categoria Standard della Toolbox. Per aggiungere un oggetto alla Web
Form è sufficiente fare doppio clic su di esso nella casella degli strumenti, una volta inserito può
essere spostato utilizzando il mouse, come si fa per i Controlli Windows. Posizioniamo così la
Label lblNome.

trattandosi di un normale form HTML, inoltre, è possibile scrivere direttamente del testo all'interno
della pagina. La Web Form dovrebbe risultare simile a quella visibile in figura.
Figura 2. Il layout della pagina

Impostiamo le proprietà degli oggetti come indicato nella tabella seguente:

Controllo (Classe) Proprietà Valore


lblNome (Label) Text Nome:
txtNome (TextBox)
btnConferma Text Conferma
(Button)
lblSaluto (Label) Text Ciao a tutti!

Cliccare qui per scaricare la pagina.

Visualizziamo il sorgente ed analizziamo il codice della pagina. Osserviamo che al pulsante


«Conferma» è associata la riga:

<asp:Button ID="btnConferma" runat="server" Text="Conferma" />

Ogni oggetto è descritto da un tag in formato XML dove gli attributi specificano le proprietà
dell'oggetto.

Aggiungiamo il delegato per la pressione del bottone. Torniamo in modalità Design e facciamo
doppio clic sul bottone per definire l'event handler corrispondente al «Click». L'ide ci mostrerà il
file con il "code behind" della pagina ed avrà preparato per noi la dichiarazione del delagato. A
questo punto è sufficiente scrivere:

protected void btnConferma_Click(object sender, EventArgs e)


{
lblSaluto.Text = "Ciao" + txtNome.Text + "!";
}

Nel sorgente della pagina è stato automaticamente aggiunto il tag


OnClick="btnConferma_Click" all'oggetto «asp:Button» definito in precedenza: poiché si tratta
di un controllo lato server (ovvero di un controllo con l'attributo runat="server" contraddistinto,
nel Designer, da una freccina verde in alto a sinistra).

In questo modo si istruisce il server che, alla pressione del pulsante, quando verrà eseguito il
PostBack della pagina, deve essere richiamato il metodo btnConferma_Click contenuto nel code
behind.

La nostra applicazione è pronta per l'esecuzione. Avviamola, digitiamo un nome nella casella di
testo e premiamo il pulsante. Otteniamo:
Figura 3. L'output del programma

Cliccare qui per scaricare l'esempio.

Proviamo a visualizzare il sorgente della pagina all'interno del browser Web (ad esempio, in
IExplorer, con il comando Visualizza>HTML), notiamo che tutto il codice ASP .NET è stato
tradotto in HTML standard.

Non c'è però traccia del codice dello script. Esso, in modo del tutto invisibile, è stato compilato in
una libreria che risiede sul server e che viene richiamata ogni volta che si effettua il post della
pagina (abbiamo infatti detto che una Web Form è un particolare tipo di form HTML).

Viewstate

Continuando ad osservare il sorgente, notiamo che nella form è stato inserito anche un campo
nascosto di nome __VIEWSTATE, che può apparire nel modo seguente:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"


value="/wEPDwUJODUwMjI0NzE3ZGSdxgdr1k5giPi+MBzwBlZ09gNTSQ==" />

Il ViewState è una tabella associativa codificata che contiene lo stato degli oggetti presenti nella
form, ad esempio il testo inserito in una casella oppure quali CheckBox sono state selezionate.

HTTP è un protocollo stateless (senza stato), ovvero non mantiene informazioni sullo stato di una
pagina tra una visita e l'altra: per sopperire a tale mancanza, ASP .NET utilizza il campo ViewState.

Ogni volta che si ricarica una pagina, i controlli in essa presenti vengono inizializzati sulla base del
contenuto del ViewState, che viene generato automaticamente da ASP .NET . Questa soluzione
consente di trasferire lo stato dei controlli dal server al client e viceversa.

Ciò significa che quando il server leggerà il ViewState di una pagina sarà in grado di ripristinare, ad
esempio, il valore corrente di tutti i campi «input», senza bisogno che sia il programmatore a farlo
via codice.

trattandosi di una tabella associativa, inoltre, nel ViewState è possibile inserire anche dei valori
personalizzati. Ad esempio, possiamo memorizzare nel ViewState il numero di volte in cui il
pulsante Conferma è stato premuto:

protected void btnConferma_Click(object sender, EventArgs e)


{
int click;
if (ViewState["click"] == null)
click = 1;
else
click = (int)ViewState["click"] + 1;

lblSaluto.Text = "Ciao " + txtNome.Text +


", hai premuto " + click + " volte";

ViewState["click"] = click;
}

In questo caso, leggiamo il valore associato alla chiave click nel ViewState: se non è stato ancora
inizializzato (ovvero vale null), significa che questa è la prima pressione del pulsante, altrimenti
incrementiamo il valore in esso contenuto. Dopo aver stampato il numero di clic, aggiorniamo il
contenuto del ViewState. Qunidi ad ogni pressione del pulsante il numero stampato aumenterà.

È possibile scaricare la nuova versione della pagina qui.

Il ViewState è mantenuto solo se vengono eseguite richieste consecutive della stessa pagina: nel
caso in cui venga caricata una nuova pagina, il ViewState sarà perso.

Per mantenere le informazioni durante la navigazione, è possibile ricorrere all'oggetto Session.


Anch'esso si comporta come una tabella associativa e funziona esattamente come il ViewState,
tuttavia le variabili archiviate al suo interno non vengono cancellate quando l'utente passa da una
pagina a un'altra dell'applicazione, ma sono mantenute per tutta la "sessione" che inizia quando un
browser punta al nostro sito e finisce quando viene chiusa.

19. I controlli Web


Come per le Windows Form anche nello sviluppo di Web Form troviamo l'aiuto di controlli che
prendono il nome di Web Controls (Controlli Web).

Al pari dei controlli Windows, anche i controoli Web sono raggruppati in categorie nella Casella
degli strumenti. In questa guida ci limiteremo a parlare dei controlli della categoria Standard, che
comprende, tra gli altri:

• Label, TextBox, Button (che abbiamo già incontrato);


• DropDownList, ListBox;
• CheckBox, RadioButton;
• CheckButtonList, RadioButtonList, che consentono di raggruppare, rispettivamente, un
insieme di CheckBox oppure di RadioButton;
• Image, ImageMap, ImageButton;
• table.
Figura 1. Toolbox di Visual Web Developer

Come al solito ci aiuteremo con un esempio pratico. Realizziamo una Web Form in cui inserire i
propri dati personali, che verranno poi inviati ad un indirizzo di posta elettronica.

Dopo aver creato un nuovo progetto Web, aggiungiamo i controlli alla Web Form in modo da
ottenere un'interfaccia analoga a quella visibile in figura:

Figura 2. Il layout della pagina

Le proprietà degli oggetti sono riassunte nella tabella seguente:

Controllo (Classe) Proprietà Valore


lblNome (Label) Text Nome:
txtNome (TextBox)
lblMail (Label) Text e-mail:
txtMail (TextBox)
lblEta (Label) Text Età:
ddlEta Items cliccando sul pulsante
(DropDownList) visualizzato a destra
della proprietà
inseriamo alcuni
intervalli di età
rblSesso Items cliccando sul pulsante
(RadioButtonList) a destra della proprietà
ed inseriamo le voci
"Maschio" (con la
proprietà Selected
uguale a true) e
"Femmina"
Repeatdirection Horizontal
lblHobby (Label) Text Hobby:
lstHobby (ListBox) Items cliccando sul pulsante
a destra della proprietà
ed inseriamo alcune
voci, l'ultima deve
chiamarsi "(Altro)"
(vedi figura)
AutoPostBack true
lblIndicaHobby Text Indicare l'hobby:
(Label) Visible False
txtHobby (TextBox) Visible False
btnInvia (Button) Text Invia

Il titolo della form è un testo scritto direttamente nella pagina all'interno del Designer.

È possibile scaricare qui la Web Form creata finora.

Passiamo alla scrittura del codice. Selezionando l'ultima voce della lista che contiene gli hobbies,
vogliamo che appaia la casella di testo in cui digitare il nome del passatempo. Allo stesso modo, se
si clicca su un'altra opzione, questa TextBox deve essere nascosta.

Clicchiamo due volte sul controllo «lstHobby» per definire l'event handler predefinito, ovvero
SelectedIndexChanged, quindi scriviamo:

protected void lstHobby_SelectedIndexChanged(object sender, EventArgs e)


{

// funziona solo se "(altro)" è l'ultma voce del menù

if (lstHobby.SelectedIndex == lstHobby.Items.Count - 1)
{
lblIndicaHobby.Visible = true;
txtHobby.Visible = true;
}
else
{
lblIndicaHobby.Visible = false;
txtHobby.Visible = false;
}
}

Avviamo l'applicazione. Cliccando su "(Altro)" compare la casella di testo, mentre con qualsiasi
altra selezione essa viene nascosta.

La form si comporta in questo modo perché abbiamo impostato la proprietà AutoPostBack della
ListBox su true.

Infatti AutopostBack è a true qualunque evento il controllo scateni viene provocato un Submit()
ovvero in PostBack e viene ricaricata la pagina.

In questo caso, ogni volta che si seleziona un elemento diverso della lista viene effettuata una
richiesta al server, il quale esegue il codice corrispondente all'evento SelectedIndexChanged.

Lasciando tale proprietà su false (valore predefinito) invece il PostBack viene causato da un altro
evento, tipicamente la pressione di un bottone.

Talvolta, per forzare l'invio anche in seguito ad altre azioni, si ricorre a codice JavaScript
introducento una chiamata a Submit(). Anche ASP .NET usa questo metodo, ma lo "nasconde"
nella proprietà AutoPostBack: quando vale true, tra i tag del controllo viene automaticamente
aggiunta una chiamata a Submit()

Per verifica impostiamo la proprietà AutoPostBack del controllo «lstHobby» su false ed eseguiamo
nuovamente l'applicazione. Questa volta, la selezione degli elementi non determina una richiesta al
server. L'evento SelectedIndexChanged sarà generato solo in seguito alla pressione del pulsante
Invia.

Aggiungiamo il codice da eseguire sul «Click» del pulsante, che deve inviare via mail le
informazioni inserite nella Web Form:

protected void btnInvia_Click(object sender, EventArgs e)


{
string Dest= "";
string MailServer = "";

MailMessage msg = new MailMessage(txtMail.Text, Dest);

msg.Subject = "Questionario";

msg.Body = "Nome: " + txtNome.Text + "nEtà: ";


msg.Body += ddlEta.SelectedValue + "nSesso: ";
msg.Body += rblSesso.SelectedValue + "nHobby: ";

if (lstHobby.SelectedIndex == lstHobby.Items.Count - 1)
{
//L'utente ha indicato un hobby che non è nell'elenco:
//Va a leggere il testo digitato nella casella.
msg.Body += txtHobby.Text;
}
else
{
//L'utente ha selezionato un hobby dell'elenco.
msg.Body += lstHobby.SelectedValue;
}

SmtpClient client = new SmtpClient(MailServer);


client.Send(msg);
}

Per utilizzare la classi MailMessage e SmtpClient, è necessario aggiungere una clausola

using System.Net.Mail;

Per completare l'applicazione, è sufficiente indicare il valore delle variabili «Destinatario» e


«MailServer», che devono contenere, rispettivamente, l'indirizzo di posta a cui verranno spediti i
dati inseriti nella form e il server mail usato per l'invio, ad esempio news.tin.it.

Per scaricare la Web Form completa, cliccare qui.

Sono disponibili anche dei controlli per la verifica degli input degli utenti. Si usano come tutti i
controlli e permettono, ad esempio, la verifica che il nome e l'indirizzo di mail siano stati
effettivamente digitati ed il formato degli indirizzi.

20. Creare un web user control


I numerosi controlli Web disponibili con il Framework .NET 2.0 consentono di creare pagine ASP
.NET ricche di funzionalità, dalla visualizzazione di un'immagine al recupero di dati da un database.

In alcune situazioni, tuttavia, potrebbero essere necessari oggetti più sofisticati di quelli predefiniti:
in questo caso possiamo creare un Web User Control (Controllo utente), utilizzando le stesse
tecniche introdotte nelle scorse lezioni.

In effetti, un Web Control può essere costruito come una Web Form: anch'esso è dotato di
un'interfaccia utente e di un file con il code behind; a differenza di una Web Form, tuttavia, è
contenuto in un file con estensione ".ascx" e non può avere i tag <HTML>, <BODY> e <FORM>
poiché viene inserito in una Web Form che questi tag li contiene già.

Anche un Web User Control è una classe ed eredita le sue funzionalità di base da
System.Web.UI.UserControl.

Per definire un Web User Control, dopo aver creato un nuovo sito ASP .NET facciamo clic con il
tasto destro del mouse sul nome del progetto all'interno del Solution Explorer e selezioniamo il
comando «Add New Item...» (Aggiungi nuovo elemento).
Si aprirà una finestra di dialogo in cui sono mostrati tutti i tipi di file che possono essere aggiunti.
Selezioniamo «Web User Control» e impostiamo il nome del file e il linguaggio in cui scrivere il
code behind.

Grazie al runtime comune del Framework .NET è possibile avere una Web Form scritta in C# al
cui interno si trova un Web Control realizzato con Visual Basic .NET, e viceversa.

Figura 1. La finestra di dialogo Add New Item

Confermiamo premendo il pulsante «Add» (Aggiungi). Il nuovo Web Control sarà al aggiunto
progetto, insieme al file contenente il code behind.

A questo punto si aprirà automaticamente la visualizzazione della pagina sorgente. Passando al


Designer, ci troviamo di fronte allo stesso editor visuale con cui si creano le Web Form.

Creiamo un semplice controllo composto da una Label e da una TextBox visto che questi due
elementi sono spesso utilizzati in coppia, avere un Web Control che li definisce entrambi in una
sola volta permette di risparmiare tempo nella creazione delle pagine.

L'interfaccia dovrebbe risultare come la seguente:

Figura 2. Il layout del Web User Control

L'impostazione delle proprietà è semplice:

Controllo (Classe) Proprietà Valore


lblEtichetta (Label) Text Etichetta
txtNome (TextBox)

Cliccare qui per scaricare il controllo.


Aggiungiamo le proprietà che consentono di lavorare con l'etichetta e la casella di testo. Dal
momento che il Web Control, una volta inserito in una pagina ASP .NET, viene visto come un
unico oggetto, deve disporre di proprietà che permettano di modificare gli oggetti contenuti al suo
interno.

Nel file del code behind definiamo le proprietà Caption e Text, per leggere e cambiare,
rispettivamente, il testo dell'etichetta e il contenuto della casella:

public string Caption


{
get { return lblEtichetta.Text; }
set { lblEtichetta.Text = value; }
} public string Text
{
get { return txtTesto.Text; }
set { txtTesto.Text = value; }
}

Il nostro Web User Control è completo e può essere prelevato cliccando qui .

Aggiungiamo questo controllo ad una pagina ASP .NET. Visualizziamo la Web Form in cui inserire
l'oggetto, quindi, all'interno del Solution Explorer, clicchiamo sul nome del Web Control (il nome
predefinito è WebUserControl.ascx).

trasciniamolo nella posizione desiderata della pagina: una volta rilasciato il mouse, il nostro Web
User Control sarà visualizzato.

La finestra delle proprietà dell'oggetto dovrebbe mostrare anche le proprietà che abbiamo definito in
precedenza; se così non fosse, è sufficiente chiudere la pagina, salvare il controllo e riaprirla.

I valori di Caption e di Text possono essere impostati sia in fase di progettazione, utilizzando la
finestra delle proprietà, sia da codice, ad esempio:

protected void Page_Load(object sender, EventArgs e)


{
WebUserControl1.Caption = "Nome:";
}

Modificando una proprietà in fase di progettazione, il Web Control non verrà aggiornato con i nuovi
valori, che, però, saranno correttamente visualizzati quando si caricherà la pagina nel browser.

Se ora diamo uno sguardo al sorgente della Web Form, notiamo che, quando si aggiunge un Web
Control ad una pagina, in essa viene inserita la direttiva @Register: si tratta di una sorta di
"include" e specifica che nella pagina si utilizza un controllo il cui codice è contenuto in un file
esterno.

Cliccare qui per scaricare un esempio di utilizzo del controllo all'interno di una Web Form.
21. La Web Control Library - (1a parte)
Oltre ai Web User Control il Framework .NET mette a disposizione un altro metodo per creare
nuovi controlli Web: la definizione di una Web Control Library.

La differenza principale tra i due tipi di controlli riguarda la fase di progettazione e consiste nella
semplicità di creazione da una parte e nella facilità d'uso dall'altra. I controlli Web sono semplici da
creare, dal momento che si realizzano in maniera visuale, dispongono del code behind e supportano
la gestione degli eventi.

Tuttavia, un Web User Control non può essere aggiunto alla Casella degli strumenti ed è
rappresentato in maniera statica quando viene aggiunto ad un pagina (ovvero la modifica di una sua
proprietà non si riflette nelle visualizzazione all'interno del Designer, ma è visibile solo in fase di
esecuzione).

Inoltre, l'unico modo di condividere il controllo tra applicazioni diverse consiste nell'inserirne una
copia distinta in ciascun progetto, soluzione che richiede una maggiore gestione nel caso in cui si
apportino modifiche al controllo.

Una Web Control Library invece in genere si realizza scrivendo a mano il codice che poi sarà
compilato. Dunque è più difficile da realizzare. Una volta realizzato il controllo, tuttavia, è possibile
aggiungerlo alla Casella degli strumenti e gestirlo in modo analogo a quanto visto per i controlli
standard.

Proviamo a creare una Web Control Library, cercando di analizzare le caratteristiche principali di
questo tipo di controlli.

Dopo aver creato un sito ASP .NET, aggiungiamo un progetto alla soluzione e selezioniamo come
tipo di progetto "Web Control Library", come evidenziato in figura.

Figura 1. La finestra di dialogo Add Project

L'editor di Visual Studio aprirà automaticamente il file "WebCustomControl1.cs". Analizziamo il


codice inserito di default nel file (alcune parti sono state omesse per motivi di spazio):

using System;
//Altra clausole using...
namespace WebControlLibrary1
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:WebCustomControl1 runat=server> </{0}:WebCustomControl1>")]
public class WebCustomControl1 : WebControl
{
private string text;

[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
public string Text
{
//Get e set della variabile "text".
}

protected override void Render(HtmlTextWriter output)


{
output.Write(Text);
}
}
}

Ogni controllo estende la classe WebControl, che fornisce proprietà, metodi ed eventi comuni a
tutti i controlli server Web.

Alla dichiarazione della classe ed alla proprietà "Text" sono stati aggiunti, tra parentesi quadre, i
cosiddetti Custom Attributes, ovvero una serie di attributi che forniscono informazioni aggiuntive
sulle classi, sui metodi e sulle proprietà.

Ad esempio, ToolboxData indica quale tag è associato al controllo: in pratica, quando tale controllo
sarà inserito in una pagina, esso sarà identificato dal tag specificato in questo attributo. Ancora,
Category specifica la categoria in cui sarà visualizzata la proprietà Text all'interno della
visualizzazione per categorie della finestra Properties.

Un'analisi dettagliata dei Custom Attributes esula dagli obiettivi di questo corso, per cui si rimanda
alla guida in linea.

Concentriamoci invece sul metodo Render(), il "cuore" del Web Control: esso, infatti, specifica
qual è il codice HTML che deve essere inserito nella pagina per visualizzare il controllo. Questa
routine riceve come argomento un oggetto di tipo HtmlTextWriter su cui scrivere il codice HTML.

Senza scendere nei dettagli, il metodo Render() viene automaticamente richiamato dal runtime del
Framework sia quando il controllo è visualizzato in una pagina ASP .NET nel Visual Web
Developer, sia quando deve essere generato il contenuto HTML da inviare al browser.

Nel primo caso, output rappresenta la pagina all'interno dell'editor, nel secondo si riferisce alla
pagina che viene inviata al client per la visualizzazione nel browser (in pratica, il codice HTML che
viene scritto dal metodo Render() è il codice che l'utente riceve quando richiede la pagina al server).
Anche i controlli Web standard si comportano nello stesso modo: ogni controllo inserito in una
pagina genera il codice HTML necessario alla sua visualizzazione tramite il metodo Render().

Ad esempio, la routine Render() di un controllo Label produce il seguente codice HTML:

<span id="Label1">Label</span>

Tornando al nostro Web Custom Control, la sua routine Render() si limita a scrivere nella pagina il
contenuto della variabile text, senza aggiungere alcuna formattazione HTML. Proviamo ad inserire
questo controllo in una Web Form.

Anzitutto dobbiamo compilare il file DLL che sarà utilizzato dall'applicazione ASP .NET. Dopo
esserci assicurati che il progetto selezionato nel Solution Explorer sia "WebControlLibrary1",
clicchiamo su comando Build>Build ebControlLibrary1.

Ora apriamo il file "Default.aspx" in visualizzazione design. La Toolbox si è arricchita di una


nuova scheda che corrisponde alla nostra Web Control Library: essa contiene una voce per ogni
classe che estende WebControl contenuta al suo interno: ognuna di esse, infatti, rappresenta un
oggetto che può essere inserito nella pagina. Per il momento c'è solo WebCustomControl1.

Figura 2. La Toolbox con il nuovo controllo Web

Aggiungiamo il nostro contollo alla pagina e, nella finestra delle proprietà, modifichiamo il valore
di Text: a differenza di quanto avviene con i Web User Control, la modifica si riflette anche in fase
di progettazione. Eseguiamo l'applicazione e verifichiamo il risultato nel browser.

Ora modifichiamo il controllo perchè il testo specificato appaia automaticamente in grassetto.


Ricordando quanro detto in precedenza, è sufficiente modificare il codice HTML generato dal
controllo, ovvero intervenire sul suo metodo Render().

Poichè il metodo Write() dell'oggetto HtmlTextWriter si limita a scrivere sulla pagina tutto quello
che riceve come argomento, un primo modo per fare quello che vogliamo potrebbe essere inserire
tutto in un'unica istruzione:

output.Write("<b>" + Text + "</b>");

Questa soluzione è corretta, ma rischia di creare confusione, inoltre la possibilità di dimenticare


qualche carattere <, > oppure / cresce all'aumentare del numero di tag da inserire.

Possiamo invece usare altri metodi messi a disposizione da HtmlTextWriter per produrre codice
più leggibile e con una distinzione maggiore tra tag HTML e contenuto vero e proprio:

protected override void Render(HtmlTextWriter output)


{
output.WriteBeginTag("b");
output.Write(HtmlTextWriter.TagRightChar);
output.Write(Text);
output.WriteEndTag("b");
}

Il metodo WriteBeginTag() scrive il carattere < e il tag indicato come argomento, ma non lo chiude
con > perchè, in generale, esso potrebbe contenere degli attributi (da specificare con il metodo
WriteAttribute()). Per questo motivo è necessaria la successiva istruzione di Write(), che scrive
semplicemente il carattere > (HtmlTextWriter.TagRightChar()).

In alternativa a queste due istruzioni, avremmo potuto usare semplicemente:

output.WriteFullBeginTag("b"); //Scrive <b>

Dopo aver stampato il testo vero e proprio, chiudiamo il tag con il metodo WriteEndTag(). Ora
ricompiliamo il controllo e visualizziamo il file "Default.aspx". Per osservare il nuovo
comportamento dell'oggetto all'interno del Designer potrebbe essere necessario chiudere e riaprire
la pagina.

Visualizzando il sorgente HTML della pagina nel browser, notiamo che il testo specificato nella
proprietà Text del controllo è stato inserito tra i tag <b> e </b>.

È possibile scaricare tutto il codice che abbiamo realizzato cliccando .

Anche questi controlli, come i controlli Web utente, una volta inseriti in una pagina prevedono l'uso
della clausola @ Register associata.

22. La Web Control Library (2a parte)


Continuiamo a parlare della Web Control Library e vediamo come creare controlli personalizzati
estendendo gli oggetti esistenti.

Realizziamo il controllo che abbiamo creato con la tecnica Web User Control (Label+TextBox),
costruendolo esclusivamente via codice. Questa soluzione ci permetterà di definire un controllo
con layout dinamico, cosa che non è possibile fare utilizzando i Web User Control.

Riprendiamo la soluzione creata nella lezione precedente e aggiungiamo un nuovo Custom Control.

Clicchiamo col tasto destro del mouse sul nome del progetto «WebControlLibrary1» nel Solution
Explorer e selezioniamo il comando Add quindi «New Item...» (Nuovo elemento).

Nella finestra di dialogo che appare è già selezionato l'elemento «Web Custom Control». Diamogli
il nome «LabelTextBox» e clicchiamo su Add.
Figura 1. La finestra di dialogo Add New Item

Questa volta il codice di default di Visual Studio è superfluo per i nostri scopi: eliminiamo la
proprietà «Text», la variabile privata «text» e il corpo del metodo Render().

Volendo realizzare una casella di testo con una caratteristica in più rispetto a quella predefinita,
facciamo in modo che il nostro nuovo controllo estenda la classe TextBox invece del generico
WebControl: così facendo, esso disporrà automaticamente di tutte le proprietà, i metodi e gli eventi
della TextBox.

public class LabelTextBox : TextBox

Ora aggiungiamo una proprietà per impostare e recuperare l'etichetta associata alla casella di testo:

private string mCaption = "Label";

[DefaultValue("Label")]
public string Caption
{
get { return mCaption; }
set { mCaption = value; }
}

Notiamo l'uso del Custom Attribute «DefaultValue», con cui si specifica qual è il valore
predefinito della proprietà (è buona norma che il valore predefinito di una proprietà sia uguale al
suo valore iniziale).

Inseriamo anche una proprietà che consente di specificare se l'etichetta deve essere visualizzata a
fianco oppure sopra la casella di testo: in questo modo il nostro controllo avrà un layout dinamico,
ovvero un layout variabile a seconda dell'impostazione delle sue proprietà:

public enum CaptionPositions


{
Side, Over
}

private CaptionPositions mCaptionPosition = CaptionPositions.Side; public CaptionPositions


CaptionPosition
{
get { return mCaptionPosition; }
set { mCaptionPosition = value; }
}

La proprietà «CaptionPosition» può assumere i valori dell'enumerazione «CaptionPositions»,


utilizzata per determinarela posizione dell'etichetta:

• Side, per mostrarla a fianco della casella di testo,


• Over, perché sia inserita al di sopra della casella di testo.

Infine, nel metodo Render() scriviamo le istruzioni che producono il codice HTML per la
visualizzazione del controllo:

protected override void Render(HtmlTextWriter output)


{
output.WriteFullBeginTag("span");
output.Write(mCaption + " ");
output.WriteEndTag("span");
if (mCaptionPosition == CaptionPositions.Over)
output.WriteBreak();
base.Render(output);
}

L'etichetta è visualizzata attraverso il tag <span> (prime tre istruzioni). Subito dopo, se si è deciso
di visualizzare l'etichetta sopra la casella di testo (mCaptionPosition == CaptionPositions.Over),
nella pagina viene inserito il tag <br />. L'ultima istruzione richiama il metodo Render() della classe
base, ovvero TextBox, che si occupa di visualizzare la casella di testo vera e propria.

Compiliamo la Web Control Library e visualizziamo nel Designer la pagina «Default.aspx». Alla
Toolbox è stato automaticamente aggiunto il nuovo controllo: clicchiamo due volte sulla voce
LabelTextBox per inserirlo nella pagina.

Figura 2. Il nuovo controllo nella Toolbox

Le proprietà di cui dispone sono tutte quelle della classica TextBox, più le due che abbiamo
definito.
Figura 3. Le nuove proprietà del controllo

Proviamo a modificare CaptionPosition: il suo cambiamento produce immediatamente un nuovo


layout per il controllo.

Figura 4. I due layout del controllo

Ereditando da TextBox il nostro controllo espone tutti gli eventi tipici dell'oggetto base. Ad
esempio, cliccando due volte su di esso, nel code behind sarà automaticamente aggiunto l'event
handler per l'evento TextChanged.

23. I file di configurazione


Il comportamento di un'applicazione ASP .NET può essere personalizzato mediante l'uso di
opportuni file di configurazione, che hanno nome «Web.config» e sono scritti in formato XML.

È possibile avere un file «Web.config» diverso in ciascuna directory del sito: ognuno di essi
applica le impostazioni di configurazione sulla propria directory e su tutte le sottodirectory.

I file di configurazione nelle sottodirectory possono stabilire configurazioni aggiuntive a quelle


ereditate dalle directory padre oppure possono sostituire o modificare quelle definite nei livelli
superiori.

Un file Web.config può contenere vari tipi di informazioni:

• variabili comuni a tutte le pagine;


• configurazioni per le sessioni;
• opzioni per la gestione degli errori;
• informazioni per la sicurezza.
Abbiamo già accennato al file Web.config in precedenza, quando lo abbiamo aggiunto al progetto
per attivare il debug dell'applicazione.

Apriamo uno qualunque dei siti Web che abbiamo realizzato nelle scorse lezioni, cliccando due
volte sul file Web.Config all'interno del Solution Explorer si aprirà un file contenente un gran
numero di commenti, inseriti automaticamente allo scopo di facilitare la modifica delle opzioni.

In effetti, prima della versione 2.0 del Framework .NET, il file Web.config poteva essere
modificato esclusivamente con un editor di testi; non esisteva alcuno strumento visuale di editing,
per cui i commenti erano fondamentali.

ASP .NET 2.0, invece, fornisce anche uno strumento grafico per la configurazione che si basa su
un'interfaccia Web ed è raggiungibile facendo cliccando su ASP .NET Configuration nel Solution
Explorer.

Figura 1. ASP .NET Configuration

Il file Web.Config è composto da due sezioni fondamentali: appSettings e system.web:

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<appSettings>

</appSettings>

<system.web>

</system.web>
</configuration>

La sezione appSettings contiene le variabili che si vogliono condividere tra tutte le pagine
dell'applicazione. Ad esempio:

<add key="Titolo" value="Guida a C#"/>


<add key="Autore" value="Marco Minerva"/>

Per recuperare i valori delle variabili Titolo ed Autore da una qualunque pagina contenuta nella
stessa directory del file Web.config, o in una sua sottodirectory, si deve utilizzare la classe
ConfigurationManager:

Label1.Text = ConfigurationManager.AppSettings["Titolo"];
Label2.Text = ConfigurationManager.AppSettings["Autore"];

La sezione system.web, invece, contiene le impostazioni proprie dell'applicazione. Nelle lezioni


precedenti abbiamo aggiunto al progetto un file Web.config con debug attivo. A questa scelta
corrisponde la seguente opzione in system.web:
<compilation debug="true" />

Di conseguenza, se vogliamo disattivare il debug per il sito Web (ad esempio perché siamo in fase
di pubblicazione), è sufficiente impostare tale proprietà su false. In questa sezione, inoltre,
attraverso il tag <customErrors>, è possibile definire la pagina da richiamare in caso di errore:

<customErrors mode="On" defaultredirect="Error.htm" />

Ogni volta che si verifica una situazione imprevista (pagina non trovata, accesso non autorizzato,
ecc.), invece del messaggio di errore predefinito sarà visualizzata la pagina Error.htm.

Oltre a Web.config, ad un sito ASP .NET può essere aggiunto un file di nome global.asax. Esso
risiede nella directory principale del sito ed è una sorta di "contenitore" di codice comune
all'applicazione. Viene impiegato, tra l'altro, per:

• eseguire operazioni all'avvio e al termine delle sessioni e dell'applicazione;


• eseguire del codice al verificarsi di una condizione di errore.

Per inserire il file global.asax ad un progetto, nella finestra di dialogo «Add New Item» è
necessario selezionare la voce Global Application Class. Il file che viene aggiunto include già le
dichiarazioni per gli event handler che possono essere definiti.

I commenti inseriti automaticamente spiegano quando i vari eventi sono generati: ad esempio, al
verificarsi di un errore, se questo non viene già intercettato dal codice presente nella pagina con un
blocco try-catch, viene scatenato l'evento Application_Error.

24. Strumenti di sviluppo alternativi


Tutti gli esempi che abbiamo realizzato in questo corso sono stati creati utilizzando Visual Studio,
l'ambiente di sviluppo Microsoft.

Questo, tuttavia, non è l'unico strumento disponibile: tra le alternative, segnaliamo SharpDevelop:
si tratta di un IDE per la realizzazione di applicazioni .NET completamente gratuito e distribuito
con il codice sorgente.
Figura 1. L'ambiente di sviluppo di SharpDevelop

Scritto in C#, riproduce abbastanza fedelmente l'ambiente di sviluppo di Visual Studio .NET 2003
(pur non in tutte le funzionalità), supporta le versioni 1.0 e 1.1 del Framework .NET, può compilare
utilizzando Mono (di cui parleremo tra breve) e consente di realizzare applicazioni in C#, Visual
Basic .NET e C++.

Dispone anche di uno strumento per l'importazione/esportazione di progetti di Visual Studio e la


traduzione di codice C# in VB .NET e viceversa.

Al momento della stesura di questa guida, inoltre, è in fase di realizzazione una nuova release di
SharpDevelop, che sarà compatibile con la versione 2.0 del Framework e supporterà le nuove
caratteristiche dell'editor di Visual Studio 2005.

Lo stesso Framework .NET di Microsoft ha una sua controparte: Mono, un'implementazione open
source del Framework che ha come obiettivo primario quello di rendere multipiattaforma le
applicazioni .NET.

Grazie a Mono è possibile scrivere applicazioni in C#, utilizzando le stesse tecniche che abbiamo
analizzato in questa guida, ed eseguirle su piattaforma Linux o Windows.

Frutto di un intenso lavoro di sviluppo da parte di Ximian e di una nutrita comunità di sviluppatori
di tutto il mondo, Mono si compone di:

• un compilatore per i linguaggi C# e VB .NET con cui è possibile sviluppare applicazioni


compatibili con la piattaforma di Microsoft;
• un compilatore che consente a Linux di far girare applicazioni scritte sotto un qualsiasi altro
sistema operativo che supporti .NET, primo fra tutti Windows;
• un modulo che aggiunge ad Apache il supporto per ASP .NET;
• un insieme di librerie che permette lo sviluppo di applicazioni utente e servizi Web in grado
di girare sotto vari sistemi operativi.

Mono comprende anche l'ambiente di sviluppo MonoDevelop, una macchina virtuale Java ed il
supporto a diversi altri linguaggi di script, tra cui Python e JScript.
Fra le maggiori lacune che attualmente si riscontrano c'è la mancanza del supporto a COM. In ogni
caso, le versioni di Mono si susseguono in maniera costante.

Attualmente Mono fornisce un supporto quasi completo della «Base Class Library» di .NET ed
integra alcuni dei nuovi controlli introdotti con ASP .NET 2.0.