C#: differenze tra internal, static, readonly e const

Scopri come utilizzare internal, static, readonly e const in C#. Differenze, esempi pratici e consigli per programmare con .NET.

Nella programmazione a oggetti con C#, le classi rappresentano uno degli elementi fondamentali per organizzare il codice e sviluppare applicazioni strutturate e facilmente manutenibili.

Oltre ai tradizionali modificatori di accesso, come public, private e protected, il linguaggio C# mette a disposizione altri costrutti che consentono di controllare la visibilità dei membri di una classe, condividere informazioni tra più oggetti e definire valori che non possono essere modificati dopo l’inizializzazione.

In questo articolo analizzeremo quattro parole chiave particolarmente utili: internal, static, readonly e const, attraverso semplici esempi pratici.

Gli assembly nell’ambiente .NET

Prima di esaminare questi costrutti è importante comprendere il concetto di assembly, uno degli elementi fondamentali dell’architettura .NET.

Un assembly rappresenta un’unità di distribuzione e organizzazione del codice compilato. Generalmente viene prodotto sotto forma di file DLL o EXE e contiene, oltre al codice, informazioni sui tipi definiti e sulle dipendenze utilizzate.

Gli assembly consentono di organizzare le applicazioni in componenti riutilizzabili, separando le funzionalità e facilitando la manutenzione del software.

Questo concetto è particolarmente importante per comprendere il funzionamento del modificatore internal.

Il modificatore internal: limitare l’accesso a un assembly

Nella programmazione a oggetti possiamo controllare l’accessibilità delle classi e dei loro membri attraverso specifici modificatori.

La parola chiave internal permette di rendere accessibile un tipo o un membro soltanto al codice appartenente allo stesso assembly.

Consideriamo questo esempio:

public class GestioneClienti
{
    internal void VerificaCliente()
    {
        Console.WriteLine("Verifica del cliente");
    }

    public void SalvaCliente()
    {
        Console.WriteLine("Salvataggio del cliente");
    }
}

Il metodo SalvaCliente() è pubblico e può essere richiamato anche da un altro assembly che abbia accesso alla classe.

Il metodo VerificaCliente(), invece, è dichiarato internal e può essere utilizzato normalmente soltanto all’interno dell’assembly in cui è stato definito.

Questa caratteristica risulta utile quando sviluppiamo librerie di classi e desideriamo mantenere alcune funzionalità disponibili ai componenti interni, senza esporle alle applicazioni che utilizzano la libreria.

Esistono anche eccezioni a questa regola, come l’attributo InternalsVisibleTo, che consente di rendere accessibili i membri interni a specifici assembly.

Il modificatore static: condividere dati tra più oggetti

Normalmente, quando creiamo un oggetto a partire da una classe, ogni istanza possiede i propri campi di istanza.

Con il modificatore static, invece, possiamo dichiarare campi e metodi che appartengono direttamente alla classe, anziché ai singoli oggetti.

Un campo statico è quindi condiviso da tutte le istanze della stessa classe.

Vediamo un esempio che permette di contare quante volte viene creato un oggetto.

using System;

class Test
{
    private static int nCounter = 0;

    public Test()
    {
        nCounter++;

        Console.WriteLine(
            $"Numero di istanze create: {nCounter}");
    }
}

class Program
{
    static void Main()
    {
        Test test1 = new Test();
        Test test2 = new Test();
        Test test3 = new Test();
    }
}

Eseguendo il programma otterremo:

Numero di istanze create: 1
Numero di istanze create: 2
Numero di istanze create: 3

La variabile nCounter viene dichiarata static e pertanto non viene creata una nuova copia per ogni oggetto.

Ogni volta che il costruttore viene eseguito, il contatore condiviso viene incrementato.

Attenzione: il valore rappresenta il numero complessivo di istanze create, non necessariamente quello degli oggetti ancora presenti in memoria.

Un campo statico mantiene il proprio valore finché rimane attivo il relativo contesto di esecuzione, ma non garantisce la persistenza dei dati dopo la chiusura dell’applicazione.

Metodi statici

Il modificatore static può essere utilizzato anche per dichiarare metodi richiamabili senza creare un’istanza della classe.

public class Calcoli
{
    public static int Somma(int a, int b)
    {
        return a + b;
    }
}

Per utilizzare il metodo è sufficiente scrivere:

int risultato = Calcoli.Somma(10, 20);

Console.WriteLine(risultato);

Il risultato sarà 30.

I metodi statici sono particolarmente utili per realizzare funzionalità di servizio che non richiedono informazioni specifiche di un oggetto.

Un metodo statico, tuttavia, non può accedere direttamente ai membri di istanza della classe senza disporre di un riferimento a un oggetto.

Il modificatore readonly: valori assegnabili durante l’inizializzazione

In alcune situazioni può essere necessario definire un campo il cui valore non debba più essere modificato dopo la costruzione dell’oggetto.

Per questo scopo C# mette a disposizione il modificatore readonly.

Un campo dichiarato readonly può essere assegnato durante la dichiarazione oppure all’interno del costruttore della classe.

Consideriamo il seguente esempio:

public class Prodotto
{
    public readonly int Codice;

    public Prodotto(int codice)
    {
        Codice = codice;
    }
}

Possiamo creare un oggetto specificando il codice:

Prodotto prodotto = new Prodotto(1001);

Console.WriteLine(prodotto.Codice);

Il valore del campo Codice sarà 1001.

Se successivamente provassimo a modificarlo:

prodotto.Codice = 2002;

Il compilatore segnalerebbe un errore.

È importante osservare che readonly non rende il campo condiviso tra tutti gli oggetti: ogni istanza della classe può avere un valore differente, stabilito durante la sua costruzione.

Inoltre, quando un campo readonly contiene un riferimento a un oggetto modificabile, il riferimento non può essere sostituito dopo l’inizializzazione, ma lo stato interno dell’oggetto potrebbe comunque essere modificato.

static readonly: un valore condiviso e non riassegnabile

I modificatori static e readonly possono essere utilizzati insieme.

In questo caso otteniamo un campo appartenente alla classe, condiviso tra le istanze e assegnabile soltanto durante l’inizializzazione statica.

public class Configurazione
{
    public static readonly DateTime DataAvvio =
        DateTime.Now;
}

Il valore può essere richiamato direttamente attraverso il nome della classe:

Console.WriteLine(Configurazione.DataAvvio);

A differenza di una costante, il valore di un campo static readonly può essere determinato durante l’esecuzione del programma.

Nell’esempio, DateTime.Now restituisce la data e l’ora al momento dell’inizializzazione del tipo, che non coincide necessariamente con l’avvio del processo.

Questa soluzione è utile quando dobbiamo condividere un valore che non può essere conosciuto durante la compilazione.

Il modificatore const: dichiarare costanti

La parola chiave const consente di dichiarare valori costanti che devono essere determinabili già durante la compilazione.

A differenza di readonly, una costante deve essere inizializzata direttamente nella dichiarazione.

public class Matematica
{
    public const double PI = 3.141592653589793;
}

Per utilizzare la costante possiamo scrivere:

double raggio = 5;

double area = Matematica.PI * raggio * raggio;

Console.WriteLine(area);

Le costanti dichiarate all’interno di una classe appartengono implicitamente al tipo e possono essere richiamate senza creare un oggetto.

Non è quindi necessario, né consentito, aggiungere esplicitamente il modificatore static a una dichiarazione const.

Le costanti possono essere utilizzate con tipi come int, double, decimal, bool, char, string ed enumerazioni, purché il valore sia un’espressione costante valida.

Non possiamo invece scrivere:

public const DateTime Data = DateTime.Now;

Questa dichiarazione genera un errore perché DateTime.Now viene valutato durante l’esecuzione e non in fase di compilazione.

In questo caso possiamo utilizzare static readonly.

Differenze tra const, readonly e static readonly

I tre costrutti vengono talvolta confusi, ma presentano caratteristiche differenti.

Caratteristicaconstreadonlystatic readonly
Valore noto in compilazioneSìNon necessarioNon necessario
Assegnazione nel costruttoreNoSì, nel costruttore di istanzaSì, nel costruttore statico
Appartiene alla classeSìNoSì
Valore diverso per ogni istanzaNoSìNo
Riassegnabile dopo l’inizializzazioneNoNoNo

Un’altra differenza importante riguarda l’utilizzo delle costanti pubbliche nelle librerie .NET.

Il valore di una costante può essere incorporato direttamente nel codice compilato che la utilizza. Se modifichiamo una costante pubblica in una DLL, le applicazioni già compilate potrebbero continuare a utilizzare il vecchio valore finché non vengono ricompilate.

Per valori che potrebbero cambiare tra versioni successive di una libreria, static readonly può quindi rappresentare una scelta più appropriata.

Quando utilizzare questi costrutti

La scelta dipende dal comportamento che desideriamo ottenere.

Utilizziamo internal quando vogliamo limitare l’accessibilità di classi o membri all’interno di un assembly.

Utilizziamo static quando un campo o un metodo deve appartenere alla classe e non a una specifica istanza.

Utilizziamo readonly quando un campo deve essere inizializzato durante la costruzione dell’oggetto e non deve essere successivamente riassegnato.

Utilizziamo static readonly quando abbiamo bisogno di un valore condiviso, inizializzato una sola volta per il tipo e non riassegnabile.

Infine, utilizziamo const per definire vere e proprie costanti note durante la compilazione.

Conclusioni

I modificatori internal, static, readonly e const rappresentano strumenti importanti per organizzare correttamente il codice C#.

Sebbene abbiano finalità differenti, permettono di controllare l’accessibilità dei componenti, condividere informazioni tra oggetti e limitare le modifiche ai dati.

Comprendere queste differenze aiuta a progettare classi più chiare, ridurre gli errori e realizzare applicazioni .NET più semplici da mantenere.

Si tratta di concetti presenti nel linguaggio da molti anni, ma ancora pienamente validi nello sviluppo moderno con C# e .NET.

Giampaolo Rossi
Giampaolo Rossi

Sviluppatore di software gestionale da oltre 28 anni.

Articoli: 325

RGPSoft© 2026 - Contenuti distribuiti con licenza CC BY 4.0