Implementare classi private, statiche e annidate
I costruttori di istanza vengono usati per specificare il codice eseguito quando si crea una nuova istanza di una classe con l'operatore new. Quando i costruttori di istanza usano il modificatore di accesso public, l'oggetto risultante è accessibile da qualsiasi altro codice nell'applicazione. Questo tipo di accesso a un oggetto e ai relativi membri è spesso auspicabile, ma in alcuni casi si vuole limitare l'accesso a una classe o ai membri di una classe.
Quando è necessario nascondere o ridurre l'esposizione di una classe, è possibile usare i modificatori di accesso per controllare la visibilità della classe e dei relativi membri.
Modificatori di accesso
Tutti i tipi e i membri del tipo hanno un livello di accessibilità che controlla se sono accessibili da altro codice nell'applicazione compilata (o da altri assembly).
Le classi dichiarate direttamente all'interno di uno spazio dei nomi possono avere accesso public, internal o file. Le classi vengono assegnate internal l'accesso per impostazione predefinita quando non viene specificato alcun modificatore di accesso.
Usare i modificatori di accesso seguenti per specificare l'accessibilità di un tipo o di un membro quando la si dichiara:
-
public: il codice in qualsiasi assembly può accedere a questo tipo o membro. Il livello di accessibilità del tipo contenitore controlla il livello di accessibilità dei membri pubblici del tipo. -
private: solo il codice dichiarato nello stessoclassostructpuò accedere a questo membro. -
protected: solo il codice nello stessoclasso in unclassderivato può accedere a questo tipo o membro. -
internal: solo il codice nello stesso assembly può accedere a questo tipo o membro. -
protected internal: solo il codice nello stesso assembly o in una classe derivata in un altro assembly può accedere a questo tipo o membro. -
private protected: solo il codice nello stesso assembly e nella stessa classe o una classe derivata può accedere al tipo o al membro. -
file: solo il codice nello stesso file può accedere al tipo o al membro.
Il modificatore record in un tipo fa sì che il compilatore sintetizza membri aggiuntivi. Il modificatore record non influisce sull'accessibilità predefinita per un record class o un record struct.
Costruttori di classi private
Un costruttore privato è un costruttore di istanza speciale. I costruttori privati vengono spesso usati nelle classi che contengono solo membri statici. Se una classe ha uno o più costruttori privati e nessun costruttore pubblico, altre classi (ad eccezione delle classi annidate) non possono creare istanze della classe . Per esempio:
class NLog
{
// Private Constructor:
private NLog() { }
public static double e = Math.E; //2.71828...
}
La dichiarazione del costruttore vuoto impedisce la generazione automatica di un costruttore senza parametri. Se non si specifica un modificatore di accesso per un costruttore, per impostazione predefinita viene private. Tuttavia, il modificatore private deve essere usato per chiarire che non è possibile creare un'istanza della classe.
I costruttori privati vengono usati per impedire la creazione di istanze di una classe quando non sono presenti campi o metodi di istanza, ad esempio la classe Math nella libreria .NET o quando viene chiamato un metodo per ottenere un'istanza di una classe. Se tutti i metodi della classe sono statici, è consigliabile rendere statica la classe completa.
Nell'esempio seguente viene illustrata una classe che usa un costruttore privato.
public class Counter
{
private Counter() { }
public static int currentCount;
public static int IncrementCount()
{
return ++currentCount;
}
}
class TestCounter
{
static void Main()
{
// If you uncomment the following statement, it generates
// an error because the constructor is inaccessible:
// Counter aCounter = new Counter(); // Error
Counter.currentCount = 100;
Counter.IncrementCount();
Console.WriteLine("New count: {0}", Counter.currentCount);
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output: New count: 101
Se si annulla il commento dell'istruzione seguente dall'esempio, il compilatore genera un errore di compilazione. Il compilatore riconosce che un costruttore privato non è accessibile.
// Counter aCounter = new Counter(); // Error
Classi statiche
Una classe statica è fondamentalmente uguale a una classe non statica, con una differenza importante: non è possibile creare un'istanza di una classe statica. In altre parole, non è possibile usare l'operatore new per creare una variabile da una classe statica. Anche se non è possibile creare un'istanza della classe, è possibile accedere ai membri di una classe statica facendo riferimento al nome della classe. Ad esempio, se si dispone di una classe statica denominata UtilityClass con un metodo statico pubblico denominato MethodA, è possibile chiamare il metodo usando una combinazione dei nomi di classe e metodo. Questa sintassi è illustrata nell'esempio seguente:
UtilityClass.MethodA();
Una classe statica può essere usata come contenitore per i metodi che operano sui parametri di input e non devono get o set alcun campo di istanza interno.
Ad esempio, nella libreria di classi .NET la classe statica System.Math contiene metodi che eseguono operazioni matematiche. Questi metodi funzionano senza alcun requisito per archiviare o recuperare dati univoci per una determinata istanza della classe Math. Per chiamare i membri della classe, specificare il nome della classe e il nome del metodo, come illustrato nell'esempio seguente:
double dub = -3.14;
Console.WriteLine(Math.Abs(dub));
Console.WriteLine(Math.Floor(dub));
Console.WriteLine(Math.Round(Math.Abs(dub)));
// Output:
// 3.14
// -4
// 3
Come accade con tutti i tipi di classe, il runtime .NET carica le informazioni sul tipo per una classe statica quando il programma che fa riferimento alla classe viene caricato. Il programma non può specificare esattamente quando viene caricata la classe. Tuttavia, è garantito il caricamento e l'inizializzazione dei relativi campi e il relativo costruttore statico chiamato prima che venga fatto riferimento alla classe per la prima volta nel programma. Un costruttore statico viene chiamato una sola volta e una classe statica rimane in memoria per la durata del dominio applicazione in cui risiede il programma.
L'elenco seguente fornisce le funzionalità principali di una classe statica:
- Contiene solo membri statici.
- Non è possibile creare un'istanza.
- È sealed.
- Non può contenere costruttori di istanza.
La creazione di una classe statica è quindi fondamentalmente identica alla creazione di una classe che contiene solo membri statici e un costruttore privato. Un costruttore privato impedisce la creazione di istanze della classe. Il vantaggio dell'uso di una classe statica è che il compilatore può verificare che nessun membro dell'istanza venga aggiunto accidentalmente. Il compilatore garantisce che le istanze di questa classe non possano essere create.
Le classi statiche sono sealed e pertanto non possono essere ereditate. Non possono ereditare da alcuna classe o interfaccia ad eccezione di Object. Le classi statiche non possono contenere un costruttore di istanza. Tuttavia, possono contenere un costruttore static. Le classi non statiche devono inoltre definire un costruttore static se la classe contiene static membri che richiedono l'inizializzazione non semplice.
Ecco un esempio di una classe statica che contiene due metodi che converte la temperatura da Celsius a Fahrenheit e da Fahrenheit a Celsius:
public static class TemperatureConverter
{
public static double CelsiusToFahrenheit(string temperatureCelsius)
{
// Convert argument to double for calculations.
double celsius = Double.Parse(temperatureCelsius);
// Convert Celsius to Fahrenheit.
double fahrenheit = (celsius * 9 / 5) + 32;
return fahrenheit;
}
public static double FahrenheitToCelsius(string temperatureFahrenheit)
{
// Convert argument to double for calculations.
double fahrenheit = Double.Parse(temperatureFahrenheit);
// Convert Fahrenheit to Celsius.
double celsius = (fahrenheit - 32) * 5 / 9;
return celsius;
}
}
class TestTemperatureConverter
{
static void Main()
{
Console.WriteLine("Please select the convertor direction");
Console.WriteLine("1. From Celsius to Fahrenheit.");
Console.WriteLine("2. From Fahrenheit to Celsius.");
Console.Write(":");
string? selection = Console.ReadLine();
double F, C = 0;
switch (selection)
{
case "1":
Console.Write("Please enter the Celsius temperature: ");
F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine() ?? "0");
Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
break;
case "2":
Console.Write("Please enter the Fahrenheit temperature: ");
C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine() ?? "0");
Console.WriteLine("Temperature in Celsius: {0:F2}", C);
break;
default:
Console.WriteLine("Please select a convertor.");
break;
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Example Output:
Please select the convertor direction
1. From Celsius to Fahrenheit.
2. From Fahrenheit to Celsius.
:2
Please enter the Fahrenheit temperature: 20
Temperature in Celsius: -6.67
Press any key to exit.
*/
Membri statici
Una classe non statica può contenere metodi statici, campi, proprietà o eventi. Il membro statico è chiamabile su una classe anche quando non esiste alcuna istanza della classe . Il membro statico è sempre accessibile dal nome della classe, non dal nome dell'istanza. Esiste una sola copia di un membro statico, indipendentemente dal numero di istanze della classe. I metodi e le proprietà statici non possono accedere a campi e eventi non statici nel tipo contenitore e non possono accedere a una variabile di istanza di qualsiasi oggetto a meno che non venga passato in modo esplicito in un parametro di metodo.
È più tipico dichiarare una classe non statica con alcuni membri statici, piuttosto che dichiarare un'intera classe come statica. Due usi comuni dei campi statici sono per mantenere un conteggio del numero di oggetti di cui viene creata un'istanza o per archiviare un valore che deve essere condiviso tra tutte le istanze.
I metodi statici possono essere sottoposti a overload ma non sottoposti a override, perché appartengono alla classe e non a nessuna istanza della classe .
Anche se un campo non può essere dichiarato come static const, un campo const è essenzialmente statico nel relativo comportamento. Appartiene al tipo, non alle istanze del tipo. È pertanto possibile accedere const campi usando la stessa notazione ClassName.MemberName usata per i campi statici. Non è necessaria alcuna istanza dell'oggetto.
C# non supporta variabili locali statiche, ovvero variabili dichiarate nell'ambito del metodo.
È possibile dichiarare membri di classe statici usando la parola chiave static prima del tipo restituito del membro, come illustrato nell'esempio seguente:
public class Automobile
{
public static int NumberOfWheels = 4;
public static int SizeOfGasTank
{
get
{
return 15;
}
}
public static void Drive() { }
public static event EventType? RunOutOfGas;
// Other nonstatic fields and properties...
}
I membri statici vengono inizializzati prima dell'accesso al membro statico per la prima volta e prima del costruttore statico, se presente, viene chiamato . Per accedere a un membro di classe statico, usare il nome della classe anziché un nome di variabile per specificare il percorso del membro, come illustrato nell'esempio seguente:
Automobile.Drive();
int i = Automobile.NumberOfWheels;
Se la classe contiene campi statici, specificare un costruttore statico che li inizializza quando viene caricata la classe.
Una chiamata a un metodo statico genera un'istruzione di chiamata in common intermediate language (CIL), mentre una chiamata a un metodo di istanza genera un'istruzione callvirt, che controlla anche la presenza di riferimenti a oggetti Null. Tuttavia, la maggior parte del tempo la differenza di prestazioni tra i due non è significativa.
Classi annidate
Un tipo class definito all'interno di un'altra classe è denominato classe annidata. Per esempio:
public class Container
{
class Nested
{
Nested() { }
}
}
Per impostazione predefinita, l'accessibilità di una classe annidata viene private. Ciò significa che le classi annidate sono accessibili solo dalla classe contenitore. Nell'esempio precedente la classe Nested non è accessibile ai tipi esterni.
È possibile specificare un modificatore di accesso per definire l'accessibilità di un tipo annidato, come indicato di seguito:
- I tipi annidati di una classe possono essere
public,protected,internal,protected internal,privateoprivate protected.
Tuttavia, la definizione di un protected, protected internal o private protected classe annidata all'interno di una classe sealed genera l'avviso del compilatore CS0628, "nuovo membro protetto dichiarato nella classe sealed".
Cautela
Tenere presente che rendere visibile esternamente un tipo annidato viola la regola di qualità del codice CA1034 "I tipi annidati non devono essere visibili".
L'esempio seguente rende pubblica la classe Nested:
public class Container
{
public class Nested
{
Nested() { }
}
}
La classe nidificata o interna può accedere alla classe contenitore, o esterna. Per accedere alla classe contenitore, passarla come argomento al costruttore della classe annidata. Per esempio:
public class Container
{
public class Nested
{
private Container? parent;
public Nested()
{
}
public Nested(Container parent)
{
this.parent = parent;
}
}
}
Una classe annidata ha accesso a tutti i membri accessibili alla relativa classe contenitore. Può accedere a membri privati e protetti della classe contenitore, inclusi tutti i membri protetti ereditati.
Nella dichiarazione precedente il nome completo della classe Nested è Container.Nested. Questo è il nome usato per creare una nuova istanza della classe annidata, come indicato di seguito:
Container.Nested nest = new Container.Nested();