Typy struktury (odwołanie w C#)
Typ struktury (lub typ struktury) to typ wartości, który może hermetyzować dane i powiązane funkcje. Słowo kluczowe służy struct
do definiowania typu struktury:
public struct Coords
{
public Coords(double x, double y)
{
X = x;
Y = y;
}
public double X { get; }
public double Y { get; }
public override string ToString() => $"({X}, {Y})";
}
Aby uzyskać informacje o ref struct
typach i readonly ref struct
typach, zobacz artykuł ref structure types (Typy struktury ref).
Typy struktur mają semantykę wartości. Oznacza to, że zmienna typu struktury zawiera wystąpienie typu . Domyślnie wartości zmiennych są kopiowane przy przypisaniu, przekazując argument do metody i zwracając wynik metody. W przypadku zmiennych typu struktury jest kopiowane wystąpienie typu. Aby uzyskać więcej informacji, zobacz Typy wartości.
Zazwyczaj typy struktur są używane do projektowania małych typów skoncentrowanych na danych, które zapewniają niewielkie lub żadne zachowanie. Na przykład platforma .NET używa typów struktur do reprezentowania liczby (zarówno liczby całkowitej , jak i rzeczywistej), wartości logicznej , znaku Unicode, wystąpienia czasu. Jeśli koncentrujesz się na zachowaniu typu, rozważ zdefiniowanie klasy. Typy klas mają semantyka odwołań. Oznacza to, że zmienna typu klasy zawiera odwołanie do wystąpienia typu, a nie samego wystąpienia.
Ponieważ typy struktur mają semantyka wartości, zalecamy zdefiniowanie niezmiennych typów struktur.
readonly
Struct
Modyfikator służy readonly
do deklarowania, że typ struktury jest niezmienny. Wszystkie elementy członkowskie readonly
danych struktury muszą być tylko do odczytu w następujący sposób:
- Każda deklaracja pola musi mieć
readonly
modyfikator - Każda właściwość, w tym automatycznie zaimplementowana, musi być tylko do odczytu lub
init
tylko. Należy pamiętać, że moduły ustawiające tylko do inicjowania są dostępne tylko w języku C# w wersji 9.
Gwarantuje to, że żaden element członkowski readonly
struktury nie modyfikuje stanu struktury. Oznacza to, że inne elementy członkowskie wystąpienia z wyjątkiem konstruktorów są niejawnie readonly
.
Uwaga
readonly
W ramach struktury element członkowski danych typu odwołania modyfikowalnego nadal może modyfikować własny stan. Na przykład nie można zastąpić List<T> wystąpienia, ale można do niego dodać nowe elementy.
Poniższy kod definiuje readonly
strukturę z inicjatorami właściwości tylko do inicjowania:
public readonly struct Coords
{
public Coords(double x, double y)
{
X = x;
Y = y;
}
public double X { get; init; }
public double Y { get; init; }
public override string ToString() => $"({X}, {Y})";
}
readonly
elementy członkowskie wystąpienia
Można również użyć readonly
modyfikatora, aby zadeklarować, że element członkowski wystąpienia nie modyfikuje stanu struktury. Jeśli nie możesz zadeklarować całego typu struktury jako readonly
, użyj readonly
modyfikatora, aby oznaczyć elementy członkowskie wystąpienia, które nie modyfikują stanu struktury.
W ramach elementu readonly
członkowskiego wystąpienia nie można przypisać do pól wystąpienia struktury. Jednak członek readonly
może wywołać członka,readonly
który nie jest członkiem. W takim przypadku kompilator tworzy kopię wystąpienia struktury i wywołuje element niebędącyreadonly
członkiem tej kopii. W związku z tym oryginalne wystąpienie struktury nie jest modyfikowane.
Zazwyczaj modyfikator jest stosowany readonly
do następujących rodzajów elementów członkowskich wystąpienia:
Metody:
public readonly double Sum() { return X + Y; }
Modyfikator można również zastosować
readonly
do metod, które zastępują metody zadeklarowane w pliku System.Object:public readonly override string ToString() => $"({X}, {Y})";
właściwości i indeksatory:
private int counter; public int Counter { readonly get => counter; set => counter = value; }
Jeśli musisz zastosować
readonly
modyfikator do obu metod dostępu właściwości lub indeksatora, zastosuj go w deklaracji właściwości lub indeksatora.Uwaga
Kompilator deklaruje metodę
get
dostępu automatycznie zaimplementowanej właściwości jakoreadonly
, niezależnie od obecnościreadonly
modyfikatora w deklaracji właściwości.Modyfikator można zastosować
readonly
do właściwości lub indeksatora za pomocąinit
metody dostępu:public readonly double X { get; init; }
Modyfikator można zastosować readonly
do pól statycznych typu struktury, ale nie do żadnych innych statycznych elementów członkowskich, takich jak właściwości lub metody.
Kompilator może używać readonly
modyfikatora do optymalizacji wydajności. Aby uzyskać więcej informacji, zobacz Unikanie alokacji.
Mutacja niestrukturalna
Począwszy od języka C# 10, można użyć with
wyrażenia do utworzenia kopii wystąpienia typu struktury z zmodyfikowanymi określonymi właściwościami i polami. Składnia inicjatora obiektów służy do określania elementów członkowskich do modyfikowania i ich nowych wartości, jak pokazano w poniższym przykładzie:
public readonly struct Coords
{
public Coords(double x, double y)
{
X = x;
Y = y;
}
public double X { get; init; }
public double Y { get; init; }
public override string ToString() => $"({X}, {Y})";
}
public static void Main()
{
var p1 = new Coords(0, 0);
Console.WriteLine(p1); // output: (0, 0)
var p2 = p1 with { X = 3 };
Console.WriteLine(p2); // output: (3, 0)
var p3 = p1 with { X = 1, Y = 4 };
Console.WriteLine(p3); // output: (1, 4)
}
record
Struct
Począwszy od języka C# 10, można zdefiniować typy struktur rekordów. Typy rekordów zapewniają wbudowane funkcje hermetyzacji danych. Można zdefiniować zarówno typy, jak record struct
i readonly record struct
. Struktura rekordów nie może być .ref struct
Aby uzyskać więcej informacji i przykładów, zobacz Rekordy.
Tablice wbudowane
Począwszy od języka C# 12, można zadeklarować tablice wbudowane jako struct
typ:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBuffer
{
private char _firstElement;
}
Tablica śródliniowa to struktura zawierająca ciągły blok N elementów tego samego typu. Jest to bezpieczny odpowiednik stałej deklaracji buforu dostępnej tylko w niebezpiecznym kodzie. Tablica śródliniowa ma struct
następujące cechy:
- Zawiera jedno pole.
- Struktura nie określa jawnego układu.
Ponadto kompilator weryfikuje System.Runtime.CompilerServices.InlineArrayAttribute atrybut:
- Długość musi być większa niż zero (
> 0
). - Typ docelowy musi być strukturą.
W większości przypadków można uzyskać dostęp do tablicy wbudowanej, takiej jak tablica, zarówno do odczytywania, jak i zapisywania wartości. Ponadto można użyć operatorów zakresu i indeksu .
Istnieją minimalne ograniczenia dotyczące typu pojedynczego pola tablicy wbudowanej. Nie może to być typ wskaźnika:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBufferWithPointer
{
private unsafe char* _pointerElement; // CS9184
}
ale może to być dowolny typ odwołania lub dowolny typ wartości:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBufferWithReferenceType
{
private string _referenceElement;
}
Tablice wbudowane można używać z niemal dowolną strukturą danych języka C#.
Tablice wbudowane to zaawansowana funkcja języka. Są one przeznaczone dla scenariuszy o wysokiej wydajności, w których wbudowany, ciągły blok elementów jest szybszy niż inne alternatywne struktury danych. Więcej informacji na temat tablic wbudowanych można znaleźć w specyfikacji funkcji
Inicjowanie struktury i wartości domyślne
Zmienna struct
typu zawiera bezpośrednio dane dla tego struct
typu . Spowoduje to rozróżnienie między niezainicjowanym struct
elementem , który ma jego wartość domyślną i zainicjowaną struct
wartością , która przechowuje wartości ustawione przez skonstruowanie. Rozważmy na przykład następujący kod:
public readonly struct Measurement
{
public Measurement()
{
Value = double.NaN;
Description = "Undefined";
}
public Measurement(double value, string description)
{
Value = value;
Description = description;
}
public double Value { get; init; }
public string Description { get; init; }
public override string ToString() => $"{Value} ({Description})";
}
public static void Main()
{
var m1 = new Measurement();
Console.WriteLine(m1); // output: NaN (Undefined)
var m2 = default(Measurement);
Console.WriteLine(m2); // output: 0 ()
var ms = new Measurement[2];
Console.WriteLine(string.Join(", ", ms)); // output: 0 (), 0 ()
}
Jak pokazano w poprzednim przykładzie, domyślne wyrażenie wartości ignoruje konstruktor bez parametrów i generuje wartość domyślną typu struktury. Wystąpienie tablicy typu struktury ignoruje również konstruktor bez parametrów i tworzy tablicę wypełniona wartościami domyślnymi typu struktury.
Najczęstszą sytuacją, w której wartości domyślne są widoczne w tablicach lub w innych kolekcjach, w których magazyn wewnętrzny zawiera bloki zmiennych. Poniższy przykład tworzy tablicę 30 TemperatureRange
struktur, z których każda ma wartość domyślną:
// All elements have default values of 0:
TemperatureRange[] lastMonth = new TemperatureRange[30];
Wszystkie pola składowe struktury muszą być zdecydowanie przypisane podczas tworzenia, ponieważ struct
typy bezpośrednio przechowują swoje dane. Wartość default
struktury zdecydowanie przypisze wszystkie pola do wartości 0. Wszystkie pola muszą być zdecydowanie przypisane po wywołaniu konstruktora. Pola są inicjowane przy użyciu następujących mechanizmów:
- Inicjatory pól można dodawać do dowolnego pola lub automatycznie zaimplementowanej właściwości.
- W treści konstruktora można zainicjować dowolne pola lub właściwości automatyczne.
Począwszy od języka C# 11, jeśli nie zainicjujesz wszystkich pól w strukturę, kompilator dodaje kod do konstruktora, który inicjuje te pola do wartości domyślnej. Kompilator wykonuje zwykłą analizę określonego przypisania. Wszystkie pola, do których uzyskuje się dostęp przed przypisaniem, lub nie są zdecydowanie przypisane, gdy konstruktor zakończy wykonywanie, mają przypisane wartości domyślne przed wykonaniem treści konstruktora. Jeśli this
dostęp do wszystkich pól zostanie przypisany, struktura zostanie zainicjowana do wartości domyślnej przed wykonaniem treści konstruktora.
public readonly struct Measurement
{
public Measurement(double value)
{
Value = value;
}
public Measurement(double value, string description)
{
Value = value;
Description = description;
}
public Measurement(string description)
{
Description = description;
}
public double Value { get; init; }
public string Description { get; init; } = "Ordinary measurement";
public override string ToString() => $"{Value} ({Description})";
}
public static void Main()
{
var m1 = new Measurement(5);
Console.WriteLine(m1); // output: 5 (Ordinary measurement)
var m2 = new Measurement();
Console.WriteLine(m2); // output: 0 ()
var m3 = default(Measurement);
Console.WriteLine(m3); // output: 0 ()
}
Każdy struct
ma public
konstruktor bez parametrów. Jeśli piszesz konstruktor bez parametrów, musi być publiczny. Jeśli struktura deklaruje dowolne inicjatory pól, musi jawnie zadeklarować konstruktor. Ten konstruktor nie musi być bez parametrów. Jeśli struktura deklaruje inicjatora pól, ale nie konstruktorów, kompilator zgłasza błąd. Każdy jawnie zadeklarowany konstruktor (z parametrami lub bez parametrów) wykonuje wszystkie inicjatory pól dla tej struktury. Wszystkie pola bez inicjatora pola lub przypisania w konstruktorze są ustawione na wartość domyślną. Aby uzyskać więcej informacji, zobacz notatkę dotyczącą propozycji funkcji konstruktorów struktury bez parametrów .
Począwszy od języka C# 12, struct
typy mogą definiować podstawowy konstruktor w ramach jego deklaracji. Konstruktory podstawowe udostępnia zwięzłą składnię parametrów konstruktora, które mogą być używane w całej struct
treści, w dowolnej deklaracji składowej dla tej struktury.
Jeśli wszystkie pola wystąpienia typu struktury są dostępne, możesz również utworzyć wystąpienie bez new
operatora . W takim przypadku należy zainicjować wszystkie pola wystąpienia przed pierwszym użyciem wystąpienia. W poniższym przykładzie pokazano, jak to zrobić:
public static class StructWithoutNew
{
public struct Coords
{
public double x;
public double y;
}
public static void Main()
{
Coords p;
p.x = 3;
p.y = 4;
Console.WriteLine($"({p.x}, {p.y})"); // output: (3, 4)
}
}
W przypadku wbudowanych typów wartości użyj odpowiednich literałów, aby określić wartość typu.
Ograniczenia dotyczące projektowania typu struktury
Struktury mają większość możliwości typu klasy . Istnieją pewne wyjątki i niektóre wyjątki, które zostały usunięte w nowszych wersjach:
- Typ struktury nie może dziedziczyć z innej klasy lub typu struktury i nie może być bazą klasy. Jednak typ struktury może implementować interfejsy.
- Nie można zadeklarować finalizatora w typie struktury.
- Przed C# 11 konstruktor typu struktury musi zainicjować wszystkie pola wystąpienia typu.
Przekazywanie zmiennych typu struktury według odwołania
Podczas przekazywania zmiennej typu struktury do metody jako argumentu lub zwracania wartości typu struktury z metody całe wystąpienie typu struktury jest kopiowane. Wartość przekazywana może mieć wpływ na wydajność kodu w scenariuszach o wysokiej wydajności, które obejmują duże typy struktur. Kopiowanie wartości można uniknąć, przekazując zmienną typu struktury według odwołania. Użyj modyfikatorów parametrów ref
, , in
out
lub ref readonly
metody, aby wskazać, że argument musi zostać przekazany przez odwołanie. Użyj funkcji ref return , aby zwrócić wynik metody według odwołania. Aby uzyskać więcej informacji, zobacz Unikanie alokacji.
ograniczenie struktury
Słowo kluczowe w ograniczeniu struct
służy struct
również do określania, że parametr typu jest typem wartości innej niż null. Oba typy struktury i wyliczenia spełniają struct
ograniczenie.
Konwersje
W przypadku dowolnego typu struktury (z wyjątkiem ref struct
typów) istnieją konwersje boxingu i rozpaski do i z System.ValueType typów i System.Object . Istnieją również konwersje boksu i rozpboxowania między typem struktury i dowolnym interfejsem, który implementuje.
specyfikacja języka C#
Aby uzyskać więcej informacji, zobacz sekcję Struktury specyfikacji języka C#.
Aby uzyskać więcej informacji na temat struct
funkcji, zobacz następujące uwagi dotyczące propozycji funkcji:
- Readonly, struktury
- Elementy członkowskie wystąpień tylko do odczytu
- C# 10 — konstruktory struktury bez parametrów
- C# 10 — zezwalaj na
with
wyrażenie w strukturach - C# 10 — struktury rekordów
- C# 11 — automatyczne struktury domyślne