I Namespace in C#

Namespace (o package) nell'OOP indica un contenitore per un gruppo di classi, accomunate da una caratteristica comune o utilizzate in un contesto condiviso.

I namespace contribuiscono a una migliore organizzazione logica del codice sorgente creando una suddivisione semantica delle classi in categorie e rendendo più semplice il loro utilizzo nel codice.

In questa lezione prenderemo in esame i namespace in C# e vedremo come utilizzarli.

Cosa sono i namespace in C#?

I namespace in C# sono gruppi nominati di classi logicamente correlate, senza alcun requisito specifico su come debbano essere collocate nel file system.

Tuttavia, è consigliato che il nome della cartella corrisponda al nome del namespace e che i nomi dei file corrispondano ai nomi delle classi definite al loro interno.

Va osservato che in alcuni linguaggi di programmazione la compilazione del codice sorgente in un determinato namespace dipende dalla distribuzione degli elementi del namespace in cartelle e file sul disco. In Java, ad esempio, questa organizzazione dei file è obbligatoria (se non viene rispettata, si verificano errori di compilazione). C#, invece, non è così rigido a riguardo.

Ora, vediamo il meccanismo di definizione dei namespace.

Definizione dei namespace

Se desideriamo creare un nuovo namespace o una nuova classe che appartenga a un dato namespace, in Visual Studio ciò avviene automaticamente tramite i comandi del menu contestuale di Solution Explorer (clic destro sulla cartella corrispondente).

Supponiamo, ad esempio, di voler creare una nuova classe chiamata MyClass all'interno del namespace MyNamespace del progetto MyConsoleApplication. Quando la creiamo, poiché il progetto si chiama MyConsoleApplication e stiamo aggiungendo nel suo folder MyNamespace, la classe appena creata sarà nel seguente namespace:

namespace MyConsoleApplication.MyNamespace

Se abbiamo definito una classe nel suo file e vogliamo aggiungerla a un namespace nuovo o già esistente, l'operazione manuale è semplice: basta modificare il blocco denominato con la parola chiave namespace all'interno della classe:

namespace nome_namespace
{
    /* ... definizione della classe ... */
}

Nella definizione utilizziamo la parola chiave namespace, seguita dal nome completo del namespace. È buona pratica che i namespace in C# inizino con una lettera maiuscola e siano scritti in Pascal Case. Ad esempio, se dobbiamo creare un namespace che contenga classi per l'elaborazione di stringhe, è preferibile chiamarlo StringUtils e non string_utils.

Namespace annidati

Oltre alle classi, i namespace possono contenere altri namespace (namespace annidati). In questo modo creiamo intuitivamente una gerarchia di namespace che permette una distribuzione ancora più precisa delle classi in base alla loro semantica.

Quando denominiamo i namespace nella gerarchia, utilizziamo il carattere . come separatore (dot notation). Ad esempio, il namespace System del .NET Framework contiene il sotto-namespace Collections; di conseguenza, il nome completo del namespace annidato Collections è System.Collections.

Nomi completi delle classi

Per comprendere pienamente il significato dei namespace, è importante sapere quanto segue:

Nota

Unicità del nome delle classi

Alle classi è richiesto di avere nomi univoci solo all'interno dei namespace in cui sono definite.

All'esterno di un determinato namespace possiamo avere classi con nomi arbitrari, indipendentemente dal fatto che coincidano con quelli di classi presenti nel namespace. Questo perché le classi all'interno del namespace sono univocamente definite nel suo contesto. È giunto il momento di vedere come definire sintatticamente questa unicità.

Con nome completo di una classe intendiamo il nome della classe preceduto dal nome del namespace in cui è definita. Il nome completo di ogni classe è univoco. Anche qui utilizziamo la notazione a punti:

nome_namespace.nome_classe

Prendiamo, ad esempio, la classe di sistema CultureInfo, definita nel namespace System.Globalization. Secondo la definizione, il nome completo della classe è System.Globalization.CultureInfo.

Nel .NET Framework talvolta esistono classi provenienti da namespace differenti con nomi coincidenti, per esempio:

System.Windows.Forms.Control
System.Web.UI.Control
System.Windows.Controls.Control

Inclusione di un namespace

Quando costruiamo un'applicazione in un determinato dominio, molto spesso è necessario utilizzare più volte le classi di un namespace. Per comodità del programmatore esiste un meccanismo di inclusione di un namespace nel file sorgente corrente. Dopo l'inclusione, tutte le classi definite in esso possono essere usate senza bisogno di scriverne i nomi completi.

L'inclusione di un namespace nel file sorgente corrente si effettua con la parola chiave using nel modo seguente:

using nome_namespace;

Prestiamo attenzione a un aspetto importante dell'inclusione dei namespace appena descritta. Tutte le classi definite direttamente nel namespace nome_namespace vengono incluse e possono essere usate, ma dobbiamo sapere quanto segue:

Nota

Inclusione non ricorsiva

L'inclusione dei namespace non è ricorsiva, cioè includendo un namespace non si includono le classi dei suoi namespace annidati.

Ad esempio, l'inclusione del namespace System.Collections non include automaticamente le classi del suo namespace annidato System.Collections.Generic. Quando servono, dobbiamo quindi usare i loro nomi completi oppure includere esplicitamente il namespace che le contiene.

Esempio di inclusione di un namespace

Per illustrare il principio di inclusione di un namespace, consideriamo il seguente programma che legge numeri, li memorizza in liste e conta quanti di essi sono interi e quanti sono double:

class NamespaceImportTest
{
    static void Main()
    {
        System.Collections.Generic.List<int> ints  =
            new System.Collections.Generic.List<int>();
        System.Collections.Generic.List<double> doubles =
            new System.Collections.Generic.List<double>();

        while (true)
        {
            int intResult;
            double doubleResult;
            Console.WriteLine("Inserisci un numero intero o un numero double:");
            string input = Console.ReadLine();

            if (int.TryParse(input, out intResult))
            {
                ints.Add(intResult);
            }
            else if (double.TryParse(input, out doubleResult))
            {
                doubles.Add(doubleResult);
            }
            else
            {
                break;
            }
        }

        Console.Write("Hai inserito {0} interi:", ints.Count);
        foreach (var i in ints)
        {
            Console.Write(" " + i);
        }
        Console.WriteLine();

        Console.Write("Hai inserito {0} double:", doubles.Count);
        foreach (var d in doubles)
        {
            Console.Write(" " + d);
        }
        Console.WriteLine();
    }
}

A tal fine il programma utilizza la classe System.Collections.Generic.List richiamandola tramite il suo nome completo.

Supponiamo di inserire consecutivamente i valori 4, 1.53, 0.26, 7, 2 e poi terminare l'immissione. Otterremo il seguente output:

Inserisci un numero intero o un numero double:
4
Inserisci un numero intero o un numero double:
1.53
Inserisci un numero intero o un numero double:
0.26
Inserisci un numero intero o un numero double:
7
Inserisci un numero intero o un numero double:
2
Inserisci un numero intero o un numero double:
Hai inserito 3 interi: 4 7 2
Hai inserito 2 double: 1.53 0.26

Il programma permette all'utente di inserire numeri in successione, che possono essere interi o double. L'inserimento prosegue finché non viene immesso un valore che non è un numero. Successivamente vengono visualizzate due righe di output, rispettivamente con i numeri interi e i numeri double.

Per implementare le operazioni descritte abbiamo usato due oggetti di supporto, rispettivamente di tipo System.Collections.Generic.List<int> e System.Collections.Generic.List<double>. È evidente che i nomi completi delle classi rendono il codice poco leggibile e scomodo. Possiamo facilmente risolvere il problema includendo il namespace System.Collections.Generic e utilizzando direttamente i nomi delle classi. Ecco la versione abbreviata del programma:

using System.Collections.Generic;

class NamespaceImportTest
{
    static void Main()
    {
        List<int> ints    = new List<int>();
        List<double> doubles = new List<double>();
        /* Il resto del codice rimane invariato */
        /* ... */
    }
}