CSharp
From MaurizioHomepage
Contents |
Il linguaggio
Ebbene sì, tra i linguaggi che mi sono messo in testa di imparare c'è anche il C#.
Ci sono parecchie idee interessanti, nonostante sia un linguaggio disegnato per evitare gli errori di programmazione e non per soddisfare i sogni erotici degli appassionati di linguaggi funzionali e/o dinamici, e di chi si diverte a mettere la maggior quantità di cose nel minor codice possibile.
Per esempio if accetta soltanto argomenti booleani, e questo serve a evitare gli errori di programmazione in cui viene usato = al posto di ==, e negli argomenti delle funzioni i riferimenti devono essere specificati (anche chiamando la funzione) con ref. Ci sono anche molte restizioni sui possibili overload degli operatori, per esempio << (shift) deve per forza avere come secondo argomento un intero, etc.
Inoltre quando si fa l'override di una funzione virtuale è necessario specificare esplicitamente se si sta facendo un override (con le keyword override) o se si sta definendo un'altra funzione con lo stesso nome (con new)
Sintassi
La sintassi è quella dei tipici linguaggi con le graffe, come C++ o Javascript. Parecchie funzionalità del linguaggio (tipo la riflessione, o gli enumeratori) sono disponibili solo attraverso la libreria, e questo rende difficile fare una recensione del linguaggio che non tenga conto anche della libreria. Se alle fine ci si trova infatti con un linguaggio parecchio prolisso, probabilmente il 90% della prolissità è dovuta alla libreria piuttosto che al linguaggio.
Purtroppo manca (e questo dovrebbe fare parte del linguaggio) una sintassi per hashtables e espressioni regolari facili, come { a: b } o { a => b } per le hashtables e /(.*)/ per le espressioni regolari. Questo è un peccato visto anche che quasi tutti gli oggetti di base implementano la funzione GetHashCode (per poter essere usati come chiavi nelle hashtables)
C'è direttamente nella sintassi invece il supporto per clausole di vario tipo per marcare un pazzo di codice, si può ad esempio lockare un oggetto (con lock, per lavorare con i thread), abilitare o disabilitare i controlli di overflow nelle operazioni numeriche (checked e unchecked), e alcune altre (si veda più avanti per altri esempi). Esempio:
lock(MyObj)
{
....
}
Non esiste il supporto per ereditarietà multipla, a meno che le classi da cu si sta derivando non siano tutte interfacce tranne una (le interfacce equivalgono grosso modo alle classi astratte del C++). Non esistono le liste di inzializzazione ma c'è un simpatico zucchero sintattico per chiamare il costruttore della classe base o per chiamare un altro costruttore dell stessa classe (cone le keywords base e this).
Il C# supporta inoltre i delegati anonimi (delegate), che permettono di definire funzioni annidate e quindi chiusure.
Esempio di delegato:
int someth = ReturnSomething();
MyDelegate addsomething = delegate(int x){ return x+someth; }
Tipi
Esistono i tipi per valore e i tipi per riferimento, e quando una variabile viene copiata succede quello che ci si aspetta che succeda a seconda se il tipo sia un valore o un riferimento. Inoltre i tipi per valore includono altre a tutti i tipi base del C anche il tipo string, e la gestione degli array è semplificata (a differenze che in C c'è una chiara distinzione fra gli array multidimensionali, a cui si accede con arr[x,y,z], e gli array di arrays a cui si accede con arr[x][y][z]).
Tutte le variabili inutilizzate vengono distrutte dal garbage collector e come al solito non si può sapere con certezza quando una variabile sarà effetivamente distrutta (si può usare la clausola using per marcare esplicitamente il tempo di vita di un oggetto).
C# e' tipato staticamente, per quanto i riferimenti siano tutti derivati dal tipo object e i valori possano invece essere inscatolati dentro un object, e oserei quindi dire che se ogni variabile ha il suo tipo, allora object fa da jolly.
Inoltre molti tipi primitivi derivano da interfacce definite nella libreria, per esempio i delegati derivano tutti dalla classe Delegate, gli arrays da Array, gli enum da Enumerable, etc.
Non esiste inferenza di tipi (verrà aggiunta nel 3.0), e non si possono aggiungere al volo membri alle istanze di una classe. Inoltre i container generici contengono degli object, ed è quindi necessario un cast esplicito quando si tira fuori una variabile di un certo tipo.
Attributi e riflessione
Una carattaristica molto interessante del C# è che possibile caratterizzare tipi, variabili e funzioni con degli attributi, che sono una sorta di metadato a cui è poi possibile accedere tramite la riflessione, ed è possibile definire qualsivoglia tipo di attributo personalizzato. E' possible ad esempio segnare con un particolare attributo personalizzato tutte le funzioni che possono essere utilizzare per un particolare scopo, mentre altri attributi di sistema possono servire a modificare il comportamento del programma (o anche del compilatore, per specificare le interazioni con codice di basso livello, cioè per i bindings). Esempio di attributo:
[MatchCommand(@"^quit$")] public void CmdQuit(Client client, RegMatch match)
Codice unsafe
C'è anche la possibilità di usare la clausola unsafe per avere accesso ai puntatori, che funzionano esattamente come in C, ed è possibile usare la clausola fixed per marcare un i pezzi di codice durante i quali non vogliamo che il garbage collector sposti il contenuto di una particole variabile.
Proprietà, eventi e indici
Il C# supporta le proprietà, che hanno la semantica di una variabile ma in realtà chiamano delle funzioni set e get quando vengono rispettivamente scritte o lette. Tali funzioni devono anche essere definite per gli indicizzatori, ovvero tutte le possibili ridefinizioni dell'operatore this[]. Simili alle proprietà sono gli eventi, che permettono di registrare e deregistrare dei delegati (da usare come gestori di eventi) e devono implementare invece delle funzioni add e remove.
Esempio di indice:
public string this[int x, int y]
{
get { return GetString(x, y); }
set { SetString(x, y, value); }
}
Enumerabili e foreach
Per tutte le classi che implementano l'interfaccia IEnumerable, come ad esempio arrays, hashtables o liste, è possibile usare il costrutto foreach per enumerare gli elementi:
foreach(int i in mylist)
{
...
}
La definizione del tipo enumerabile deve contenere una funzione GetEnumerator che chiami yield return x per ogni elemento x nel contenitore:
IEnumerator GetEnumerator()
{
yield return 2;
yield return 3;
yield return 5;
yield return 7;
...
}
yield deve essere chiamato per forza direttamente dalla funzione GetEnumerator (e non da una possibile funzione intermedia), insomma il C# implementa delle coroutines ad hoc per il foreach, e non è possible usare questo comportamente per emulare delle vere coroutines!!! (vedi anche: Perché yield fa schifo in C#)
Generic
A partire dal C# 2.0 sono stati aggiunti al linguaggio i generic, che sono l'equivalente delle templates del C++ con alcune differenze. Una differenza fondamentale è che i generic possono solo dipendere da un tipo e non da un valore, e quindi non è possibile implementare alcun tipo di template metaprogramming (a differenza di quello che si può fare in C++). Invece, una cosa interessante che c'è in C# e' la keyword where che permette di specificare i requisiti che deve avere un tipo per poter usare il generic:
void MyFunc<T>(T t) where T: MyClass, new()
{
...
}
Nel codice qui sopra, si richiede che il tipo T derivi da MyClass e che abbia un costruttore che non prende parametri.
Considerazioni
Questa è l'opinione che mi sono fatta del linguaggio:
Pro
- E' Microsoft!
- Attributi e buona riflessione.
- Le stringhe sono unicode.
- E' disegnato nel modo migliore possibile perché le classi rappresentino dei widgets (proprietà, eventi, etc).
- Cerca di evitare i più comuni errori di programmazione (soprattutto quelli del C/C++), forzando chi programma ad essere chiaro.
- Buon supporto integrato nel linguaggio per i thread (e anche per l'interazione coi databases nel 3.0)
- E' utilizzabile in un ambiente gestito protetto, completo e multipiattaforma (grazie a Mono) strettamente legato al linguaggio (visto che molte cose del linguaggio si rifanno alla libreria).
Contro
- E' Microsoft!
- E' troppo prolisso! (almeno per i miei gusti, soprattutto per quanto riguarda hashtables/regex e containers).
- E' limitante per certe cose (tipo i generic, o gli operatori)
- Certe cose sembrano implementate ad hoc solo per essere usate in una sola situazione (tipo yield)
- Le stringhe sono tutte costanti e bisogna usare usare la classe StringBuilder per avere stringhe variabili, e questa non è l'unica cosa apparentemente semplicissima che risulta essere incasinata (tipo convertire unicode in bytes).
- Non si può riaprie le classi.
- E' troppo legato alla sua libreria di classi.
