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.
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;
}
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.
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.
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.
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:
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
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.
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.
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.
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...
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.
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:
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.
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:
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:
È 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:
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):
I namespace
namespace Prova
{
public class Classe1
{}
Con questo costrutto, per dichiarare, ad esempio, un oggetto di tipo Classe1 è necessario scrivere:
lo stesso vale per Classe2. Fatto questo, è possibile accedere ai metodi ed alle proprietà delle classi
nel modo consueto. I namespace sono sempre public.
namespace N1
{
namespace N2
{
public class Classe1
{}
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;
Il tipo struct permette di definire nuovi tipi di dati a partire da quelli esistenti:
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:
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.
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:
Stati s = Stati.America;
System.Console.WriteLine("Stato selezionato: " + s.ToString());
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.
int i = 7;
Calcola(i);
int i = 7;
CalcolaRef(ref i);
//All'uscita, i vale i + 1 = 8
//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:
//...
}
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):
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à.
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.
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
stud.Cognome = "Minerva"
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:
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:
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.
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.
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.
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:
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:
Come si vede, nella classe base la parola chiave «virtual» è sparita, mentre in Studente il metodo
StampaMessaggio() è preceduto da «new».
È 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.
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:
L'overloading viene spesso usato nella definizione del costruttore di una classe. Riprendiamo il
costruttore della nostra classe Persona:
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:
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";
}
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".
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:
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».
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.
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:
//Stampa true
Console.WriteLine(p1 == 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»:
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:
return (object)this == o;
}
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.
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.
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.
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.
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»:
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):
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:
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:
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.
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.
• 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.
• 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à).
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.
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).
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.
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.
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:
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.
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.
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:
L'istruzione «throw new Exception()» solleva una eccezione. Ci occuperemo delle eccezioni in
seguito.
• 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:
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.
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#.
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.
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:
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:
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.
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:
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:
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.
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:
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».
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);
}
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.
È 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.
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:
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.
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».
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:
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.
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:
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
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:
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
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:
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:
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à.
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.
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:
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:
Il titolo della form è un testo scritto direttamente nella pagina all'interno del Designer.
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:
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:
msg.Subject = "Questionario";
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;
}
using System.Net.Mail;
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.
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.
Confermiamo premendo il pulsante «Add» (Aggiungi). Il nuovo Web Control sarà al aggiunto
progetto, insieme al file contenente il code behind.
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.
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:
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:
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.
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".
}
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().
<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.
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.
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:
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:
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()).
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>.
Anche questi controlli, come i controlli Web utente, una volta inseriti in una pagina prevedono l'uso
della clausola @ Register associata.
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.
Ora aggiungiamo una proprietà per impostare e recuperare l'etichetta associata alla casella di testo:
[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à:
Infine, nel metodo Render() scriviamo le istruzioni che producono il codice HTML per la
visualizzazione del controllo:
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.
Le proprietà di cui dispone sono tutte quelle della classica TextBox, più le due che abbiamo
definito.
Figura 3. Le nuove proprietà 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.
È 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.
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.
<?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:
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"];
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:
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:
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.
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++.
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:
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.