C# szerkezetek

Jótanács

Új szoftverfejlesztés? Először az Első lépések oktatóanyagokkal kezdje. Ha egyszerűsített értéktípusokra van szüksége a kódban, a szerkezetekkel találkozik.

Tapasztalt egy másik nyelven? A C# struktúrák olyan értéktípusok, amelyek hasonlóak a C++ vagy Swift struktúrákhoz, de amikor dobozolva vannak, a felügyelt halmon helyezkednek el, és támogatják a felületeket, konstruktorokat és metódusokat. A C#-specifikus minták olvasható szerkezetek szakaszának átfésülése. A rekordstruktúra a Rekordok című témakörben olvasható.

A szerkezet olyan értéktípus, amely közvetlenül a példányban tárolja az adatait, nem pedig a halom egy objektumára mutató hivatkozáson keresztül. Amikor új változóhoz rendel egy szerkezetet, a futtatókörnyezet a teljes példányt átmásolja. Az egyik változó módosítása nincs hatással a másikra, mert mindegyik változó egy másik példányt jelöl. Szerkezetek használata olyan kisméretű, egyszerűsített típusokhoz, amelyek elsődleges szerepe az adatok tárolása a modellezési viselkedés helyett. Ilyenek például a koordináták, a színek, a mérések vagy a konfigurációs beállítások.

Mikor érdemes használni a szerkezeteket?

Használjon struktúrát, ha a típus:

  • Egyetlen értéket vagy a kapcsolódó értékek kis csoportját jelöli (nagyjából 16 bájt vagy kevesebb).
  • Értékszemantikával rendelkezik – két azonos adattal rendelkező példánynak egyenlőnek kell lennie.
  • Ez elsősorban egy adattároló, nem pedig egy viselkedési modell.
  • Nem igényel öröklést egy alaptípusból (a szerkezetek nem örökölhetnek más szerkezetektől vagy osztályoktól, de interfészeket implementálhatnak).

Az osztályokat, rekordokat, tuple-öket és interfészeket tartalmazó szélesebb körű összehasonlításért tekintse meg a Típus kiválasztása című témakört.

Deklarálj egy struktúrát

Adjon meg egy szerkezetet a struct kulcsszóval. A szerkezetek ugyanúgy tartalmazhatnak mezőket, tulajdonságokat, metódusokat és konstruktorokat, mint egy osztályt:

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

A Point szerkezet két double értéket tárol, és egy metódust biztosít a két pont közötti távolság kiszámításához. A DistanceTo metódus azért van megjelölve readonly , mert nem módosítja a szerkezet állapotát. Ezt a mintát olvasható tagok fedik le.

Érték szemantikája

A szerkezetek értéktípusok. A hozzárendelés átmásolja az adatokat, így minden változó saját független másolatot tartalmaz:

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

Mivel a szerkezetek adattárolók, a hozzárendelés minden adattagot egy új, független példányba másol. Minden másolat eltérő. Az egyik módosítása nincs hatással a másikra. Ez a viselkedés eltér az osztályoktól, ahol a hozzárendelés csak a hivatkozást másolja, és mindkét változó ugyanazt az objektumot használja. A megkülönböztetésről további információt az Értéktípusok és a referenciatípusok című témakörben talál.

Szerkezetkonstruktorok

Konstruktorokat ugyanúgy definiálhat a szerkezetekben, mint az osztályokban. A szerkezetek paraméter nélküli konstruktorokkal is rendelkezhetnek, amelyek egyéni alapértelmezett értékeket állíthatnak be. A "paraméter nélküli konstruktor" kifejezés megkülönbözteti a konstruktorlogikát new futtató példányt a kifejezéssel létrehozott default példánytól (amely az összes mezőt nullával inicializálja):

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

Paraméter nélküli konstruktor akkor fut, ha argumentumok nélkül használja new . A default kifejezés megkerüli a konstruktort, és beállítja az összes mezőt az alapértelmezett értékekre (0, null, false). Vegye figyelembe a különbséget:

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)

A fordító automatikusan inicializálja a konstruktorban nem explicit módon beállított mezőket. Csak azokat a mezőket inicializálhatja, amelyek nem alapértelmezett értékeket igényelnek:

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
    }
}

Az alábbi példa a következő alapértelmezett értéket IsBlockedjeleníti meg:

var tile = new GameTile(2, 5);
Console.WriteLine($"Tile ({tile.Row}, {tile.Column}), blocked: {tile.IsBlocked}");
// Tile (2, 5), blocked: False

A IsBlocked tulajdonság nincs hozzárendelve a konstruktorban, ezért a fordító false értékre állítja (ami az alapértelmezett bool esetén). Ez a funkció csökkenti a konstruktorokban a sablonkódot, amelyeknek csak néhány mezőt kell beállítaniuk.

Csak olvasható struktúrák és csak olvasható tagok

Garantálja, hogy a readonly struct egyetlen példánytag sem módosítja a struktúra állapotát. A fordító ezt a garanciát úgy érvényesíti, hogy az összes mezőnek és az automatikusan implementált tulajdonságnak írásvédettnek kell lennie.

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

Az alábbi példa létrehoz egy Temperature példányt, és beolvassa annak tulajdonságait.

var temp = new Temperature(100);
Console.WriteLine(temp); // 100.0°C (212.0°F)
// temp.Celsius = 50; // Error: property is read-only

Ha nincs szüksége a teljes szerkezetre, hogy megváltoztathatatlan legyen, jelölje meg az egyes tagokat readonly helyette. A readonly tag nem tudja módosítani a szerkezet állapotát, és a fordító ellenőrzi, hogy a következő garancia van-e:

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

Az alábbi példa azt mutatja be, hogy a readonly tagok frissített értékeket adnak vissza a változó tulajdonságok változásakor:

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...

A readonly tagok megjelölésével segíthetünk a fordítónak a védelmi másolatok optimalizálásában. Amikor egy paramétert elfogadó readonly metódusnak ad át egy in szerkezetet, a fordító tudja, hogy nincs szükség másolatra.

Lásd még