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})";
}

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

Począwszy od języka C# 7.2, modyfikator readonly służy 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. W języku C# 9.0 lub nowszym właściwość może mieć metodęinit dostępu.

Gwarantuje to, że żaden element członkowski readonly struktury nie modyfikuje stanu struktury. W języku C# 8.0 lub nowszym 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 zestawami właściwości tylko inicjowania dostępnymi w języku C# 9.0 lub nowszym:

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

Począwszy od języka C# 8.0, 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ć ich do pól wystąpienia struktury. Jednak członek readonly może wywołać element niebędącyreadonly członkiem. W takim przypadku kompilator tworzy kopię wystąpienia struktury i wywołuje element inny niżreadonly element członkowski na 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 do właściwości zaimplementowanej automatycznie jako readonly, niezależnie od obecności readonly modyfikatora w deklaracji właściwości.

    W języku C# 9.0 lub nowszym można zastosować readonly modyfikator 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 Pisanie bezpiecznego i wydajnego kodu w języku C#.

Mutacja niestruktoryjna

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ć typy record struct i readonly record struct . Struktura rekordów nie może być strukturąref. Aby uzyskać więcej informacji i przykłady, zobacz Rekordy.

Inicjowanie struktury i wartości domyślne

Zmienna struct typu zawiera bezpośrednio dane dla tego structtypu . Powoduje to rozróżnienie między niezainicjowanym structelementem , który ma jego wartość domyślną i zainicjowaną structwartoś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 zobaczysz wartości domyślne, jest tablica lub inne kolekcje, 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 jego 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 dodać 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 ramach struktury, 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, zostaną przypisane ich 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 nie napiszesz publicznego konstruktora bez parametrów, kompilator wygeneruje go. Wygenerowany konstruktor bez parametrów kompilator wykonuje dowolne inicjowanie pola i tworzy wartość domyślną dla wszystkich pozostałych pól. W przypadku deklarowania jakichkolwiek inicjatorów pól należy zadeklarować jeden jawny konstruktor. Jednym jawnym konstruktorem może być konstruktor bez parametrów. Może mieć puste ciało. Aby uzyskać więcej informacji, zobacz uwaga dotycząca propozycji funkcji konstruktorów struktury bez parametrów .

Jeśli wszystkie pola wystąpienia typu struktury są dostępne, można 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 obrębie typu struktury.
  • Przed C# 11 konstruktor typu struktury musi zainicjować wszystkie pola wystąpienia typu.
  • Przed C# 10 nie można zadeklarować konstruktora bez parametrów.
  • Przed C# 10 nie można zainicjować pola lub właściwości wystąpienia w jego deklaracji.

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 przez 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. refUżyj modyfikatorów parametrów , outlub in 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 Pisanie bezpiecznego i wydajnego kodu w języku C#.

ref Struct

Począwszy od języka C# 7.2, można użyć ref modyfikatora w deklaracji typu struktury. ref Wystąpienia typu struktury są przydzielane na stosie i nie mogą uciec do zarządzanego stert. Aby upewnić się, że kompilator ogranicza użycie ref typów struktur w następujący sposób:

  • Struktura ref nie może być typem elementu tablicy.
  • Struktura ref nie może być zadeklarowanym typem pola klasy lub niezwiązanymref ze strukturą.
  • Struktura ref nie może implementować interfejsów.
  • Nie ref można skonstruować struktury do System.ValueType lub System.Object.
  • Struktura ref nie może być argumentem typu.
  • Zmiennej ref struktury nie można przechwycić za pomocą wyrażenia lambda ani funkcji lokalnej.
  • Nie ref można użyć zmiennej struktury w metodzie async . Można jednak użyć ref zmiennych struktury w metodach synchronicznych, na przykład w metodach, które zwracają Task lub Task<TResult>.
  • Nie ref można używać zmiennej struktury w iteratorach.

Począwszy od języka C# 8.0, można zdefiniować jednorazową ref strukturę. W tym celu upewnij się, że ref struktura pasuje do jednorazowego wzorca. Oznacza to, że ma wystąpienie lub metodę rozszerzenia Dispose , która jest dostępna, bez parametrów i ma zwracany void typ.

Zazwyczaj typ struktury definiuje ref się, gdy potrzebny jest typ, który zawiera również składowe ref danych typów struktur:

public ref struct CustomRef
{
    public bool IsValid;
    public Span<int> Inputs;
    public Span<int> Outputs;
}

Aby zadeklarować ref strukturę jako readonly, połącz readonly modyfikatory i ref w deklaracji typu ( readonly modyfikator musi występować przed ref modyfikatorem):

public readonly ref struct ConversionRequest
{
    public ConversionRequest(double rate, ReadOnlySpan<double> values)
    {
        Rate = rate;
        Values = values;
    }

    public double Rate { get; }
    public ReadOnlySpan<double> Values { get; }
}

Na platformie .NET przykłady ref struktury to System.Span<T> i System.ReadOnlySpan<T>.

ograniczenie struktury

Użyjesz również słowa kluczowego struct w ograniczeniustruct, aby określić, że parametr typu jest typem wartości innej niż null. Zarówno typy struktury, jak i wyliczenia spełniają struct ograniczenie.

Konwersje

W przypadku dowolnego typu struktury (z wyjątkiem ref typów struktur ) istnieją konwersje boxing i rozpboxing do i z System.ValueType typów i System.Object . Istnieją również konwersje boxing i rozpboxing między typem struktury a dowolnym interfejsem, który implementuje.

specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz sekcję Strukturyspecyfikacji języka C#.

Aby uzyskać więcej informacji na temat funkcji wprowadzonych w języku C# 7.2 lub nowszym, zobacz następujące uwagi dotyczące propozycji funkcji:

Zobacz też