Implementowanie klas prywatnych, statycznych i zagnieżdżonych
Konstruktory wystąpień służą do określania kodu wykonywanego podczas tworzenia nowego wystąpienia klasy za pomocą operatora new. Gdy konstruktory wystąpień używają modyfikatora dostępu public, wynikowy obiekt jest dostępny z dowolnego innego kodu w aplikacji. Ten typ dostępu do obiektu i jego składowych jest często pożądany, ale czasami chcesz ograniczyć dostęp do klasy lub do składowych klasy.
Gdy musisz ukryć lub zmniejszyć ekspozycję klasy, możesz użyć modyfikatorów dostępu, aby kontrolować widoczność klasy i jej składowych.
Modyfikatory dostępu
Wszystkie typy i składowe typu mają poziom ułatwień dostępu, który kontroluje, czy są dostępne z innego kodu w skompilowanej aplikacji (lub innych zestawach).
Klasy zadeklarowane bezpośrednio w przestrzeni nazw mogą mieć dostęp do public, internal lub file. Klasy są przypisywane internal dostępu domyślnie, gdy nie określono modyfikatora dostępu.
Użyj następujących modyfikatorów dostępu, aby określić dostępność typu lub elementu członkowskiego podczas deklarowania go:
-
public: Kod w dowolnym zestawie może uzyskać dostęp do tego typu lub elementu członkowskiego. Poziom ułatwień dostępu zawierający typ kontroluje poziom ułatwień dostępu publicznych elementów członkowskich typu. -
private: tylko kod zadeklarowany w tym samymclasslubstructmoże uzyskać dostęp do tego elementu członkowskiego. -
protected: tylko kod w tym samymclasslub w pochodnejclassmoże uzyskać dostęp do tego typu lub elementu członkowskiego. -
internal: tylko kod w tym samym zestawie może uzyskać dostęp do tego typu lub elementu członkowskiego. -
protected internal: tylko kod w tym samym zestawie lub w klasie pochodnej w innym zestawie może uzyskać dostęp do tego typu lub składowej. -
private protected: tylko kod w tym samym zestawie i w tej samej klasie lub klasie pochodnej może uzyskać dostęp do typu lub składowej. -
file: tylko kod w tym samym pliku może uzyskać dostęp do typu lub elementu członkowskiego.
Modyfikator record na typie powoduje, że kompilator syntetyzuje dodatkowe elementy członkowskie. Modyfikator record nie ma wpływu na domyślne ułatwienia dostępu dla record class lub record struct.
Konstruktory klasy prywatnej
Konstruktor prywatny jest specjalnym konstruktorem wystąpienia. Konstruktory prywatne są często używane w klasach, które zawierają tylko statyczne elementy członkowskie. Jeśli klasa ma co najmniej jeden konstruktor prywatny i nie ma publicznych konstruktorów, inne klasy (z wyjątkiem klas zagnieżdżonych) nie mogą tworzyć wystąpień klasy. Na przykład:
class NLog
{
// Private Constructor:
private NLog() { }
public static double e = Math.E; //2.71828...
}
Deklaracja pustego konstruktora uniemożliwia automatyczne generowanie konstruktora bez parametrów. Jeśli nie określisz modyfikatora dostępu dla konstruktora, wartość domyślna to private. Jednak modyfikator private powinien służyć do wyjaśnienia, że nie można utworzyć wystąpienia klasy.
Konstruktory prywatne są używane do zapobiegania tworzeniu wystąpień klasy, gdy nie ma pól lub metod wystąpienia, takich jak klasa Math w bibliotece platformy .NET lub gdy wywoływana jest metoda w celu uzyskania wystąpienia klasy. Jeśli wszystkie metody w klasie są statyczne, rozważ utworzenie kompletnej klasy statycznej.
W poniższym przykładzie przedstawiono klasę używającą konstruktora prywatnego.
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
Jeśli usuń komentarz z poniższej instrukcji z przykładu, kompilator generuje błąd kompilacji. Kompilator rozumie, że prywatny konstruktor jest niedostępny.
// Counter aCounter = new Counter(); // Error
Klasy statyczne
Klasa statyczna jest zasadniczo taka sama jak klasa niestatyczna, z jedną ważną różnicą: nie można utworzyć wystąpienia klasy statycznej. Innymi słowy, nie można użyć operatora new do utworzenia zmiennej na podstawie klasy statycznej. Chociaż nie można utworzyć wystąpienia klasy, możesz uzyskać dostęp do składowych klasy statycznej, odwołując się do nazwy klasy. Jeśli na przykład masz klasę statyczną o nazwie UtilityClass, która ma publiczną metodę statyczną o nazwie MethodA, możesz wywołać metodę przy użyciu kombinacji nazw klas i metod. Ta składnia jest pokazana w poniższym przykładzie:
UtilityClass.MethodA();
Klasę statyczną można użyć jako kontenera dla metod, które działają na parametrach wejściowych i nie muszą get ani set żadnych wewnętrznych pól wystąpienia.
Na przykład w bibliotece klas platformy .NET statyczna klasa System.Math zawiera metody wykonujące operacje matematyczne. Te metody działają bez konieczności przechowywania lub pobierania danych unikatowych dla określonego wystąpienia klasy Math. Elementy członkowskie klasy są wywoływane przez określenie nazwy klasy i nazwy metody, jak pokazano w poniższym przykładzie:
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
Podobnie jak w przypadku wszystkich typów klas środowisko uruchomieniowe platformy .NET ładuje informacje o typie dla klasy statycznej, gdy program odwołujący się do klasy jest ładowany. Program nie może określić dokładnie, kiedy klasa jest ładowana. Jednak gwarantowane jest załadowanie i zainicjowanie pól oraz wywołanie jego konstruktora statycznego przed odwołaniem do klasy po raz pierwszy w programie. Konstruktor statyczny jest wywoływany tylko raz, a klasa statyczna pozostaje w pamięci przez okres istnienia domeny aplikacji, w której znajduje się program.
Poniższa lista zawiera główne funkcje klasy statycznej:
- Zawiera tylko statyczne elementy członkowskie.
- Nie można utworzyć wystąpienia wystąpienia.
- Jest zapieczętowany.
- Nie można zawierać konstruktorów wystąpień.
Dlatego tworzenie klasy statycznej jest w zasadzie takie samo jak tworzenie klasy, która zawiera tylko statyczne elementy członkowskie i konstruktor prywatny. Konstruktor prywatny uniemożliwia utworzenie wystąpienia klasy. Zaletą używania klasy statycznej jest to, że kompilator może sprawdzić, czy nie zostały przypadkowo dodane żadne elementy członkowskie wystąpienia. Kompilator gwarantuje, że nie można utworzyć wystąpień tej klasy.
Klasy statyczne są zapieczętowane i dlatego nie można ich dziedziczyć. Nie mogą dziedziczyć z żadnej klasy ani interfejsu z wyjątkiem Object. Klasy statyczne nie mogą zawierać konstruktora wystąpienia. Mogą jednak zawierać konstruktor static. Klasy niestatyczne powinny również definiować konstruktor static, jeśli klasa zawiera static składowe, które wymagają inicjalizacji nietrygalnych.
Oto przykład klasy statycznej zawierającej dwie metody konwertujące temperaturę z Stopni Celsjusza na Fahrenheit i Fahrenheit na Stopnie Celsjusza:
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.
*/
Statyczne elementy członkowskie
Klasa niestatyczna może zawierać metody statyczne, pola, właściwości lub zdarzenia. Statyczny element członkowski można wywołać w klasie nawet wtedy, gdy nie istnieje żadne wystąpienie klasy. Statyczny element członkowski jest zawsze uzyskiwany przez nazwę klasy, a nie nazwę wystąpienia. Istnieje tylko jedna kopia statycznego elementu członkowskiego, niezależnie od liczby wystąpień klasy. Metody statyczne i właściwości nie mogą uzyskać dostępu do niestatycznych pól i zdarzeń w ich typie zawierającym i nie mogą uzyskać dostępu do zmiennej wystąpienia dowolnego obiektu, chyba że jawnie przekazano go w parametrze metody.
Bardziej typowe jest deklarowanie klasy niestatycznej z niektórymi statycznymi elementami członkowskimi niż deklarowanie całej klasy jako statycznej. Dwa typowe zastosowania pól statycznych to zachowanie liczby wystąpień obiektów lub przechowywanie wartości, która musi być współdzielona między wszystkimi wystąpieniami.
Metody statyczne mogą być przeciążone, ale nie przesłaniane, ponieważ należą do klasy, a nie do żadnego wystąpienia klasy.
Chociaż nie można zadeklarować pola jako static const, pole const jest zasadniczo statyczne w jego zachowaniu. Należy do typu, a nie do wystąpień typu. W związku z tym dostęp do pól const można uzyskać przy użyciu tej samej notacji ClassName.MemberName używanej dla pól statycznych. Nie jest wymagane żadne wystąpienie obiektu.
Język C# nie obsługuje statycznych zmiennych lokalnych (czyli zmiennych zadeklarowanych w zakresie metody).
Składowe klas statycznych są deklarowane przy użyciu słowa kluczowego static przed typem zwrotnym elementu członkowskiego, jak pokazano w poniższym przykładzie:
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...
}
Statyczne elementy członkowskie są inicjowane przed uzyskaniem dostępu do statycznego elementu członkowskiego po raz pierwszy i przed wywołaniem konstruktora statycznego. Aby uzyskać dostęp do statycznej składowej klasy, użyj nazwy klasy zamiast nazwy zmiennej, aby określić lokalizację składowej, jak pokazano w poniższym przykładzie:
Automobile.Drive();
int i = Automobile.NumberOfWheels;
Jeśli klasa zawiera pola statyczne, podaj konstruktor statyczny, który inicjuje je podczas ładowania klasy.
Wywołanie metody statycznej generuje instrukcję wywołania w typowym języku pośrednim (CIL), podczas gdy wywołanie metody wystąpienia generuje instrukcję callvirt, która również sprawdza odwołania do obiektów null. Jednak w większości przypadków różnica wydajności między nimi nie jest znacząca.
Klasy zagnieżdżone
Typ class zdefiniowany w innej klasie jest nazywany klasą zagnieżdżona. Na przykład:
public class Container
{
class Nested
{
Nested() { }
}
}
Dostępność zagnieżdżonej klasy jest domyślnie ustawiona na private. Oznacza to, że zagnieżdżone klasy są dostępne tylko z ich klasy zawierającej. W poprzednim przykładzie klasa Nested jest niedostępna dla typów zewnętrznych.
Modyfikator dostępu można określić, aby zdefiniować dostępność typu zagnieżdżonego w następujący sposób:
- Zagnieżdżone typy klasy mogą być
public,protected,internal,protected internal,privatelubprivate protected.
Jednak zdefiniowanie klasy protected, protected internal lub private protected zagnieżdżonej wewnątrz zapieczętowanej klasy generuje ostrzeżenie kompilatora CS0628" "nowy chroniony element członkowski zadeklarowany w klasie zapieczętowanej".
Ostrożność
Należy pamiętać, że zewnętrznie widoczny typ zagnieżdżony narusza regułę jakości kodu CA1034 "Typy zagnieżdżone nie powinny być widoczne".
W poniższym przykładzie klasa Nested jest publiczna:
public class Container
{
public class Nested
{
Nested() { }
}
}
Klasa zagnieżdżona lub wewnętrzna może uzyskiwać dostęp do klasy zawierającej lub zewnętrznej. Aby uzyskać dostęp do zawierającej klasy, przekaż ją jako argument do konstruktora klasy zagnieżdżonej. Na przykład:
public class Container
{
public class Nested
{
private Container? parent;
public Nested()
{
}
public Nested(Container parent)
{
this.parent = parent;
}
}
}
Klasa zagnieżdżona ma dostęp do wszystkich składowych, które są dostępne dla jej zawierającej klasę. Może ona uzyskiwać dostęp do prywatnych i chronionych składowych zawierającej klasę, w tym do wszystkich dziedziczynych chronionych elementów członkowskich.
W poprzedniej deklaracji pełna nazwa Nested klasy to Container.Nested. Jest to nazwa użyta do utworzenia nowego wystąpienia zagnieżdżonej klasy w następujący sposób:
Container.Nested nest = new Container.Nested();