C# esetén kezelési szempontból kétféle típus létezik. Vannak az értéktípusok és a referenciatípusok. Az értéktípusok minden függvényhívás esetén értékként lesznek átadva. Ez azt jelenti, hogy konkrétan nem a változó van átadva a metódusnak híváskor, hanem annak az értéke, vagyis hívás előtt másolódik az érték, tehát az adat nagyon rövid ideig 2x van benne a memóriában.
Ennek a viselkedésmódnak egyszerű típusok, mint számok, szövegek1 esetén van értelme. De nem csak ezen típusok viselkednek így, hanem minden struktúrából származtatott objektum. Az egyszerű típusok egyébként struktúraként vannak megvalósítva a keretrendszerben.
A referenciatípusok lényegében az osztályok. Ezek esetén a metódushíváskor az osztály referenciája másolódik, ami azt jelenti, hogy csak egyszer van jelen az objektum a memóriában. Ez nagy mennyiségű adat tárolásánál jó, de néha problémát okoz, ha referencia helyett értékre lenne szükségünk.
A nagy mennyiségű adattárolás igen relatív fogalom lehet. Lehetőség szerint ha a megvalósítandó típusunknak több, mint 16 byte adatot kell tárolnia, akkor class kulcsszóval lássuk el, vagyis legyen osztály. Ha a tárolt adatok mennyisége ennél kevesebb, akkor lehet nyugodtan struktúra.
Az alábbi példa egy egyszerű struktúra létrehozását mutatja be:
using System;
namespace PeldaStruktura
{
public struct Pont
{
//16 byte adat :)
public double x, y;
}
class Program
{
static void Main(string[] args)
{
//nem kell hívni konstruktort
Pont pont;
//de cserébe minden tagot inicializálni kell értékkel
//ha ez elmarad, akkor fordítási hibát kapunk
pont.x = 23;
pont.y = 11;
Console.WriteLine("x: {0}, y: {1}", pont.x, pont.y);
Console.ReadKey();
}
}
}
A program kimenete:
x: 23, y: 11
A következő példa pedig az értékátadás problémáit mutatja be:
using System;
namespace PeldaAtadas
{
class Osztaly
{
public double ertek;
}
class Program
{
static void ErtekatadoPelda(double ertek)
{
ertek = 2.1;
}
static void ReferenciaPelda(Osztaly referencia)
{
referencia.ertek = 2.1;
}
static void Main(string[] args)
{
Console.WriteLine("Érték típus példa");
Console.WriteLine();
double ertek = 3.14;
Console.WriteLine("Függvényhívás előtt az ertek: {0}", ertek);
ErtekatadoPelda(ertek);
Console.WriteLine("Függvényhívás után az ertek: {0}", ertek);
Console.WriteLine();
Console.WriteLine("Referencia típus példa");
Console.WriteLine();
Osztaly o = new Osztaly();
o.ertek = 3.14;
Console.WriteLine("Függvényhívás előtt az ertek: {0}", o.ertek);
ReferenciaPelda(o);
Console.WriteLine("Függvényhívás után az ertek: {0}", o.ertek);
Console.ReadKey();
}
}
}
A program kimenete:
Érték típus példa
Függvényhívás előtt az ertek: 3,14
Függvényhívás után az ertek: 3,14
Referencia típus példa
Függvényhívás előtt az ertek: 3,14
Függvényhívás után az ertek: 2,1
A példa esetén jól látható, hogy az érték típusok esetén, ha egy függvény megpróbálja a függvényen belül módosítani a paraméterként kapott értéket, akkor csak a függvényen belül lesz maradandó a változás, mivel az eredeti érték másolatát kapta meg. A függvényhívás végeztével az eredetileg átadott érték nem módosul, míg a referencia típusoknál igen, mert ott ténylegesen a változó kerül átadásra, nem annak értéke.
Nézzünk még egy példát:
using System;
namespace PeldaAtadasKomplikalt
{
public class Osztaly
{
public double Ertek { get; }
public Osztaly(double ertek)
{
Ertek = ertek;
}
public void Kiir()
{
Console.WriteLine(Ertek);
}
}
class Program
{
public static void Modosit(Osztaly osztaly)
{
osztaly = new Osztaly(99);
}
static void Main(string[] args)
{
var pelda = new Osztaly(44);
pelda.Kiir();
Modosit(pelda);
pelda.Kiir();
Console.ReadKey();
}
}
}
A program kimenete:
44
44
Az eredmény meglepő lehet, így elemezzük mi is történik itt. A Modosit metódus egy Osztaly típusú referencia típust vár. Meg is kapja a pelda referencia másolatát, ami felülír egy új Osztaly típusú referenciával. Mivel a másolatot kapta meg, ezért a metódusból kilépve ez a másolt referencia megszűnik létezni a módosított értékével együtt. Ezért marad a pelda ugyan az mind a két kiírás között.
Összegezve: a metódus meg tudja változtatni a paraméterül kapott referencia típusú objektumot, viszont a referenciát nem tudja egy másik objektumhoz rendelni, erre lesz majd jó a ref módosító.
-
A .NET esetén a string típus valójában egy osztály, ami igyekszik úgy viselkedni, mint egy érték típus. Ez nem tervezési hiba, a keretrendszer sebességét növeli.↩