Udostępnij za pośrednictwem


Struktury języka C#

Wskazówka

Dopiero zaczynasz programować oprogramowanie? Najpierw zacznij od samouczków Wprowadzenie . Struktury będą spotykane, gdy potrzebujesz lekkich typów wartości w kodzie.

Czy masz doświadczenie w pracy w innym języku? Struktury języka C# są typami wartości podobnymi do struktur w językach C++ lub Swift, ale gdy są zapakowane, znajdują się na zarządzanej stercie oraz obsługują interfejsy, konstruktory i metody. Przejrzyj sekcję readonly structs pod kątem wzorców charakterystycznych dla języka C#. Aby uzyskać informacje o strukturach rekordów, zobacz Rekordy.

Struktura jest typem wartości, który przechowuje swoje dane bezpośrednio w instancji, a nie poprzez odwołanie do obiektu na stosie. Po przypisaniu struktury do nowej zmiennej środowisko uruchomieniowe kopiuje całe wystąpienie. Zmiany w jednej zmiennej nie wpływają na drugą, ponieważ każda zmienna reprezentuje inne wystąpienie. Używaj struktur dla małych, lekkich typów, których podstawowa rola to przechowywanie danych, a nie modelowanie zachowań. Przykłady obejmują współrzędne, kolory, pomiary lub ustawienia konfiguracji.

Deklarowanie struktury

Zdefiniuj strukturę za pomocą słowa kluczowego struct . Struktura może zawierać pola, właściwości, metody i konstruktory, podobnie jak klasa:

struct Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public readonly double DistanceTo(Point other)
    {
        var dx = X - other.X;
        var dy = Y - other.Y;
        return Math.Sqrt(dx * dx + dy * dy);
    }

    public override string ToString() => $"({X}, {Y})";
}

Struktura Point przechowuje dwie double wartości i udostępnia metodę obliczania odległości między dwoma punktami. Metoda DistanceTo jest oznaczona readonly , ponieważ nie modyfikuje stanu struktury. Ten schemat jest opisany w członkach tylko do odczytu.

Semantyka wartości

Struktury są typami wartości. Przypisanie kopiuje dane, więc każda zmienna przechowuje własną niezależną kopię:

var p1 = new Point { X = 3, Y = 4 };
var p2 = p1; // copies the data
p2.X = 10;

Console.WriteLine(p1); // (3, 4)  — p1 is unchanged
Console.WriteLine(p2); // (10, 4) — only p2 was modified

Ponieważ struktury to kontenery danych, przypisanie kopiuje każdy element członkowski danych do nowego, niezależnego wystąpienia. Każda kopia jest odrębna. Modyfikowanie jednego nie ma wpływu na drugą. To zachowanie różni się od klas, w których przypisanie kopiuje tylko odwołanie, a obie zmienne współużytkują ten sam obiekt. Aby uzyskać więcej informacji na temat rozróżnienia, zobacz Typy wartości i typy referencyjne.

Konstruktory structów

Konstruktory można definiować w strukturach tak samo jak w klasach. Struktury mogą mieć konstruktory bez parametrów , które ustawiają niestandardowe wartości domyślne. Termin "konstruktor bez parametrów" rozróżnia wystąpienie utworzone za pomocą new (które uruchamia logikę konstruktora) od wystąpienia domyślnego utworzonego za pomocą wyrażenia default (które zeruje wszystkie pola):

struct ConnectionSettings
{
    public string Host { get; set; }
    public int Port { get; set; }
    public int MaxRetries { get; set; }

    public ConnectionSettings()
    {
        Host = "localhost";
        Port = 8080;
        MaxRetries = 3;
    }
}

Konstruktor bez parametrów jest uruchamiany w przypadku użycia new bez argumentów. Wyrażenie default pomija konstruktora i ustawia wszystkie pola na wartości domyślne (0, null, false). Należy pamiętać o różnicy:

var custom = new ConnectionSettings();
Console.WriteLine($"{custom.Host}:{custom.Port} (retries: {custom.MaxRetries})");
// localhost:8080 (retries: 3)

var defaults = default(ConnectionSettings);
Console.WriteLine($"{defaults.Host ?? "(null)"}:{defaults.Port} (retries: {defaults.MaxRetries})");
// (null):0 (retries: 0)

Kompilator automatycznie inicjuje wszystkie pola, które nie są jawnie ustawiane w konstruktorze. Można zainicjować tylko pola, które wymagają wartości innych niż domyślne:

struct GameTile
{
    public int Row { get; set; }
    public int Column { get; set; }
    public bool IsBlocked { get; set; }

    public GameTile(int row, int column)
    {
        Row = row;
        Column = column;
        // IsBlocked is automatically initialized to false
    }
}
var tile = new GameTile(2, 5);
Console.WriteLine($"Tile ({tile.Row}, {tile.Column}), blocked: {tile.IsBlocked}");
// Tile (2, 5), blocked: False

Właściwość IsBlocked nie jest przypisana w konstruktorze, więc kompilator ustawia ją na false (domyślną dla bool). Ta funkcja zmniejsza potrzebę szablonowego kodu w konstruktorach, które muszą ustawić tylko kilka pól.

Struktury readonly i elementy członkowskie tylko do odczytu

To readonly struct gwarantuje, że żaden członek instancji nie modyfikuje stanu struktury. Kompilator wymusza tę gwarancję, wymagając, aby wszystkie pola i właściwości zaimplementowane automatycznie były tylko do odczytu:

readonly struct Temperature
{
    public double Celsius { get; }

    public Temperature(double celsius) => Celsius = celsius;

    public double Fahrenheit => Celsius * 9.0 / 5.0 + 32.0;

    public override string ToString() => $"{Celsius:F1}°C ({Fahrenheit:F1}°F)";
}
var temp = new Temperature(100);
Console.WriteLine(temp); // 100.0°C (212.0°F)
// temp.Celsius = 50; // Error: property is read-only

Jeśli nie potrzebujesz, aby cała struktura była niezmienna, oznacz poszczególne człony jako readonly zamiast. Członek readonly nie może zmodyfikować stanu struktury, a kompilator sprawdza, czy gwarantuje to:

struct Velocity
{
    public double X
    {
        readonly get;
        set;
    }

    public double Y
    {
        readonly get;
        set;
    }

    public readonly double Speed => Math.Sqrt(X * X + Y * Y);

    public readonly override string ToString() => $"({X}, {Y}) speed={Speed:F2}";
}
var v = new Velocity { X = 3, Y = 4 };
Console.WriteLine(v.Speed); // 5
Console.WriteLine(v);       // (3, 4) speed=5.00
v.X = 6;
Console.WriteLine(v.Speed); // 7.211...

Oznaczanie członków readonly ułatwia kompilatorowi optymalizowanie kopii zabezpieczających. Po przekazaniu readonly struktury do metody, która akceptuje in parametr, kompilator wie, że kopia nie jest potrzebna.

Kiedy należy używać struktur

Użyj struktury, gdy typ:

  • Reprezentuje pojedynczą wartość lub małą grupę powiązanych wartości (mniej więcej 16 bajtów lub mniej).
  • Ma wartościową semantykę, co oznacza, że dwa wystąpienia z tymi samymi danymi powinny być równe.
  • Jest to przede wszystkim kontener danych, a nie model zachowania.
  • Nie wymaga dziedziczenia z typu podstawowego (struktury nie mogą dziedziczyć z innych struktur lub klas, ale mogą implementować interfejsy).

Aby uzyskać szersze porównanie obejmujące klasy, rekordy, krotki i interfejsy, zobacz Wybierz rodzaj typu.

Zobacz także