Property init
Az előző alfejezetben kitértünk az immutable típusok esetén a csúnya és hosszú konstruktorok problémájára. Önmagában ezt a record
nem oldja meg. Éppen ezért a C# 9.0 bevezeti a get
és set
kulcsszavak mellé az init
kulcsszót, amit tulajdonságok létrehozásához használhatunk.
Az init
a set
helyett használható. Ha egy property init
jelöléssel van ellátva, akkor csak olvasható, de az objektum létrehozásának pillanatában kaphat értéket. Ez lehetővé teszi az object initializer szintaxis használatát, ami elkerülhetővé teszi a konstruktorok írását.
Az előzőekben tárgyalt Point
recordunk a módosítás után:
record Point
{
public double X { get; init; }
public double Y { get; init; }
}
//példányosítás
Point a = new Point
{
X = 1;
Y = 2;
}
A new operátor
Az objektumok példányosításnál újdonság, hogy nem kell kiírni a típus nevét a new
operátor után, ha a kifejezés bal oldalából látszik a típus, vagyis a var
és a new()
együtt nem működőképes, mert a fordítónak esélye sincs kitalálni, hogy mit is szeretnénk létrehozni.
Point a = new Point(); //"régimódi"
Point a = new(); //default ctor hívása
//Fordítási hiba:
//var a = new();
A new()
nem csak nem csak lokális változóknál használható:
class Pelda
{
protected readonly Dictionary<string, int> cache = new();
}
Required kulcsszó
Az init
property bevezetése egy jó irány volt a konstruktorok kiváltására, de nem mentett meg minket minden esetben. Például adott egy ilyen osztály:
class Tanulo
{
public string Keresztnev { get; init; }
public string Vezeteknev { get; init; }
}
Mivel a Keresztnev
és a Vezeteknev
is referencia (string
) típusúak, ezért null
értékük lesz. Ha ezt el szeretnénk kerülni, akkor egy paraméter nélküli konstruktort kell írnunk, ami üres szövegre inicializálja őket, ha az object initializer szintaxisban valamelyiknek nem adott értéket a kód felhasználója. Ha meg már konstruktort írunk, akkor nyugodtan lehet paraméteres és akkor már példányosításkor rá vagyunk kényszerítve az értékadásra.
Ezen változtat a C# 11-ben bevezetettrequired
kulcsszó, ami kikényszeríti, hogy egy adott property a példányosításkor meg legyen adva:
class Tanulo
{
public required string Keresztnev { get; init; }
public required string Vezeteknev { get; init; }
}
//Fordítási hiba, mivel a Vezeteknev nem lett megadva:
//var t = new Tanulo
//{
// Keresztnev = "Teszt";
//}
A required
kulcsszó nem csak init
property-vel működik, hagyományos, bármikor írható set
property-k esetén is alkalmazható, illetve érték típusok esetén is működik:
class Point
{
public required double X { get; set; }
public required double Y { get; set; }
}
Primary constructors
A record
típus definiálható egy soros formában is. Például az előző példában szereplő Tanulo
osztály definiálható így is:
public record class Tanulo(string Keresztnev, string Vezeteknev);
//ekvivalens
public record class Tanulo(string Keresztnev, string Vezeteknev)
{
}
Ezt a szintaxisú definiálást nevezzük primary avagy elsődleges konstruktor szintaxisnak. Az ebben a formában megadott record
működésileg azonos lesz a Tanulo
osztállyal, annyi különbséggel, hogy a keresztnév és vezetéknév értékeket konstruktor hívás argumentumaként tudjuk átadni. A konstruktor argumentumoknál szándékosan nagy kezdőbetűvel lettek megadva a paraméterek, mert a paraméterek nevével megegyező publikus tulajdonságok jönnek létre az osztályon belül.
Ez a szintaxis C# 12 óta nem csak a record
módosítóval ellátott osztályok esetén alkalmazható, hanem struktúrák és hagyományos osztályok esetén is. Itt működésben annyi különbség van, hogy a record
esetén a primary constructor tulajdonságokat hoz létre, „hagyományos” típusok esetén pedig privát tag változókat, amik nem kapnak readonly
módosítót.
Ha egy primary constructorral rendelkező típus esetén egy vagy további konstruktort definiálnánk, akkor a konstruktornak láncolva kell lennie a primary constructor hívásra:
class Point(int x, int y)
{
//primary constructort meg kell hívni
public Point() : this(0, 0)
}
Ez igaz öröklés esetén is:
class Point(int x, int y)
{
}
class Point3D(int x, int y, int z) : Point(x, y);