Tagok ismétlése
Korábban láttuk azt, hogy létre tudunk hozni tagokat olyan összetett típusokhoz, mint osztály és struktúra. Ezek a tagok a következők lehettek:
- Field-ek, amik egy objektum belső állapotát tárolják (Összetett típusok és példányaik )
- Tag Metódusok, amik az adott objektummal kapcsolatos műveleteket tudnak végrehajtani (Összetett típusok tagmetódusai )
- Property-k, amik egyszerű, biztonságos és jól szabályozható hozzáférést tesznek lehetővé objektum belső állapotához (A Property, azaz tulajdonság )
Ezen tagok, mindig egy konkrét objektumon értelmezettek. Úgy értük el őket, hogy leírtuk az objektum nevét, utána a .
(pont) operátort, végül meg az adott tagot.
Person p1 = new Person();
p1.name = "Tivadar"; // A pont operátor használata
string name = p1.name; // A pont operátor használata
A pont operátorra gondolhatunk úgy, hogy általa érünk el mindent, ami egy objektumon belül van: mezőket, metódusokat, property-ket.
Példa
Most képzeljünk el egy Car
, azaz autó osztályt.
using System;
class Car
{
// Publikus field-ek:
public int year; // Mikor készült
public string model; // Mi a típusa
public float maxSpeed = 100; // Mekkora a maximális sebessége
// Privát field:
float _currentSpeed = 0; // Jelenlegi sebesség
// Publikus metódusok:
public float GetSpeed() // Sebesség lekérdezése
{
return _currentSpeed;
}
public void SetSpeed(float newValue) // Sebesség beállítása
{
_currentSpeed = Math.Clamp(newValue, 0, maxSpeed);
}
// Publikus property:
public float RelativeSpeed // A jelenlegi és maximális sebesséfg aránya
{
get => _currentSpeed / maxSpeed;
set => _currentSpeed = maxSpeed * Math.Clamp(value, 0, 1);
}
}
Ebből az autó (Car
) típusból aztán létrehozhatunk példányokat:
Car myCar = new Car(); // Létrehozunk egy példányt a Car osztályból
myCar.year = 1969; // int field
myCar.model = "FordMustang"; // string field
myCar.maxSpeed = 150; // float field
myCar.SetSpeed (100); // Metódus hívása
myCar.RelativeSpeed = 0.5f; // Property használata
Car yourCar = new Car(); // Létrehozunk egy másik példányt a Car osztályból
// ... beállítjuk ezt is ...
Mint az fent látható, a tagok mindig egy adott objektumhoz kötődnek: Csak egy konkrét objektum-on, azaz az osztálynak egy konkrét példányán értelmezzük őket.
Például a myCar
-nak a model
field-je tartalmazhat más értéket, mint a yourCar
-é. Ugyanígy a GetSpeed
metódust is csak úgy van értelme hívni, hogy azt egy példányon tesszük. Vagy a myCar
-tól kérdezzük meg, hogy mi a sebesség vagy a yourCar
-tól, és különböző válaszokat kaphatunk függően attól, mely objektumon hívtuk a függvényt.
float s1 = myCar.GetSpeed(); // Egyik autó sebességét kérjük le
float s2 = yourCar.GetSpeed(); // Másik autó sebességét kérjük le
Értelmetlen az, ha egyszerűen felteszem a kérdést: “Mekkora a sebesség?” anélkül, hogy megmondanám, hogy mi az aminek a sebességét szeretném megtudni.
Ugyanígy tehát ha nincs referenciánk egy Car
objektumra, nem tudjuk lekérdezni annak sebességét a GetSpeed
metódussal. Más szavakkal, a GetSpeed
függvényhívásnak csak akkor van értelme, ha van egy Car
típusú objektumom, amin el tudom végezni azt.
Ez miden tagra (field, tagmetódus, property) igaz. Mindegyik szorosan kötődik egy objektumhoz, egy példányhoz, és értelmezhetetlenek nélkülük.
Statikus tagok
Ezzel szemben állnak a statikus tagjai egy összetett típusnak.
(Ha simán azt mondjuk, tagok vagy members, az azt jelenti, hogy NEM statikus tagokról beszélünk.)
A statikus tagok a C# nyelvben olyan tagok, amelyek közvetlenül a típus alapján érhetők el, és nem csak egy adott objektumon értelmezettek, hanem a típuson magán. A statikus tagoknak nincs szükségük példányra a használatukhoz.
Egy statikus taghoz a típus nevén keresztül férünk hozzá és nem a típus egy példányán, objektumán keresztül, mint azt a (nem statikus) tagok esetén tettük.
Statikus metódusok
Most képzeljük el, hogy azt szeretnénk lekérdezni, hogy autók egy egész tömbjéből melyik a leggyorsabb. Egy kicsit más jellegű ez a kérdés, mint az, amikor egy adott autó sebességét kértük le. Ezt a kérdést nem igazán lenne értelme egy konkrét autónak feltenni.
Képzeljünk el egy függvényt, ami összehasonlítja autók egy tömbjét aszerint, hogy mekkora a sebességük és visszaadja a leggyorsabbikat. Ez a metódus logikailag az autó osztály körül végez műveletet, ezért a Car
osztályon belül definiáljuk, de nem egy konkrét autón értelmezett, hanem autók egy egész tömbje részt vesz benne.
Mindezért lehet ez a Car
osztály egy statikus metódusa, amit valahogy így lehetne definiálni:
class Car
{
// ... Ide kerülnek a tagok a korábbi kódrészletből ...
// A Car osztály egy statikus metódusa:
public static Car GetFastest(Car[] cars)
{
if (cars == null || cars.Length == 0)
return null;
float maxSpeed = -1;
Car fastest = null;
foreach (Car car in cars)
{
if (car.maxSpeed > maxSpeed)
{
maxSpeed = car.maxSpeed;
fastest = car;
}
}
return fastest;
}
}
Statikus tag definiáláshoz használjuk a static
kulcsszót
Ezután a metódust a következőképp tudnánk használni:
Car myCar = new Car();
Car yourCar = new Car();
// ... Autók beállítása ...
Car[] carArray = {myCar, yourCar };
Car fastestCar = Car.GetFastest(carArray );
Figyeljük meg, hogy most a .
operátor előtt a Car
típusnév szerepel és nem egy példány.
Statikus változók
Nem csak statikus metódusai, de statikus field-jei és property-jei is létezhetnek osztályoknak és struktúráknak. Egy összetett típus statikus változóiból csakis egy db létezik a memóriában függetlenül attól, hogy hány példányunk létezik az adott osztályból.
// Nem példányon keresztül érjük el a statikus field-eket.
Car.staticField = 451; // A típus nevére hivatkozunk.
Console.WriteLine(Car.staticField); // 451
Gondolhatunk úgy a statikus field-ekre, hogy olyan változók ezek, amiken az egyes példányok megosztoznak. Tehát az egész programban csak egy van belőle. Minden egyes autónak van külön sebessége, szóval az nem statikus, de ha eltároljuk az összes autó számát, vagy átlagsebességét, akkor ahhoz egy statikus változó kell.
Statikus osztályok
Ha egy osztály csak statikus tagot tartalmaz, akkor készíthetünk belőle egy statikus osztályt. Ekkor a definiálásánál a static
kulcsszót kell tennünk a class
elé. Ezáltal megtiltjuk, hogy nem statikus tagot definiáljunk az adott osztályban.
Statikus osztályból nem fogunk tudni példányt létrehozni.
Statikus osztályokra lehet úgy tekinteni, mint változók és metódusok egy gyűjteménye, ami nem kötődik egy-egy objektumhoz.
Ilyen osztályok szoktak lenni például a matematikai könyvtárak, mint a Math
vagy Unity-ben a Mathf
. Segítségükkel sok általános matematikai metódushoz férünk hozzá a kód bármelyik részéből. Nem kell hozzá egy példány.
Statikusok előnyei és hátrányai
Statikus tagok hasznosak akkor, amikor egy bizonyos információt (field-et) vagy műveletet (metódust) bármikor bárhonnan el szeretnénk érni, anélkül, hogy szükség lenne egy objektum referenciára. Ezáltal segítségükkel nagyban tudjuk egyszerűsíteni az architektúrát.
Másik előnye a statikusoknak, hogy sok esetben az elérésük gyorsabb is, mint a nem statikus tagoké.
Persze csak akkor használhatunk statikus mezőt, metódust vagy osztályt, ha az érintett adatból csak egy van. Sajnos viszont eléggé gyakori, hogy később jövünk rá, hogy egy típusú adatból több példány is kellene. Megírunk valamit statikusan és utána felismerjük, hogy mégis sok példányban szeretnénk használni azt.
Vegyük a következő példát: Van egy életpontja a játékosnak, amit tudnak sebezni az ellenfelek és csapdák, ezért ezt a változót vagy akár egész osztályt (PlayerHealthPoint) statikussá tesszük. Sok fejlesztés után viszont rájövünk, hogy több játékossal szeretnénk játszani a játékot, vagy hogy az ellenfeleknek is lehet élete. Ekkor az életpont nem lehet már statikus, hiszen több is van belőle. Helyette valami módon mindig meg kell tudni, hogy mit akarunk sebezni és azon végrehajtani a műveletet.
Ez persze nem a világ vége, egyszerűen csak refaktorálni kell a kódbázist:
// Statikus verzió:
PlayerHealthPoint.Damage(10);
// Általános verzió:
if(collider.TryGetComponent(out HealthPoint hp))
hp.Damage(10);
Javasolt azonban minderre előre készülni, utólagos módosítgatások mindig macerásabbak. Persze mikor először megírunk egy szkriptet, sosem tudhatjuk 100% biztosra, hogy több helyen is kelleni fog-e egy adat, viszont ha látunk erre némi esélyt, érdemes lehet kapásból nem statikusként felvenni azt és előre elvégezni az extra munkát, ami ezzel jár.
Mindazonáltal emiatt teljesen elvetni a statikusok használatát hiba lenne. Sok helyen semmi hátrány nélkül egyszerűsíti és optimalizálja a kódot, márpedig semmi értelme feleslegesen bonyolítani azt.
Ezt úgy is szokták mondani a programozásban, hogy “K.I.S.S.” - Keep It Simple Stupid 🙂
Top level statements
Korábban tárgyaltuk, hogy a teljes C# projekten belül csak egy file tartalmazhat futtatható utasítások sorozatát anélkül, hogy azok egy összetett típusban (és azon belül egy metódusban) helyezkednének el.
Ezeket “legfelsőbb szintű utasítások”-nak vagy “Top level statement”-eknek nevezzük és ez a kód lesz a programunk belépési pontja.
Az itt definiált metódusok hasonlítanak a statikusokra, hívhatók anélkül, hogy kéne hozzá egy példány.