A ref módosító alkalmazható struktúrákra is. Ha ezt alkalmazzuk, akkor egy referencia struktúrát kapunk, ami a stack-en allokálódik és sosem kerülhet át a heap-re. Éppen ezért számos limitációt alkalmaz az ilyen típusok esetén a fordító:
- Nem lehet tömb eleme
- Nem lehet része egy osztálynak és hagyományos struktúrának
- Nem implementálhat interfészeket
- Nem dobozolható az object ősosztályra
- Nem használható generikus típus argumentumnak
- Nem használható lambda és lokális függvényekben
- Nem használható async metódusokban
- Nem használható iterátorokban
Ezen limitációkat olvasva felmerülhet, hogy akkor hol és mire érdemes ezeket a típusokat alkalmazni?
A referencia struktúrákat akkor érdemes használni sima struktúrákkal szemben, ha a struktúránk életciklusa rövid és sok példányt vagy gyakran kell belőle allokálni. Ebben az esetben, ha ref módosítóval látjuk el a sruktúránkat, akkor csökkenteni tudjuk a nyomást a GC-n, mivel a megkötések miatt sosem kerülhet ki példány a heap-re.
Saját kódunkban ref struct típussal akkor is találkozhatunk, ha a Span<T> és ReadOnlySpan<T> típusokat alkalmazzuk, mivel ezek valójában referencia struktúraként vannak implementálva.
C# 11 óta a referencia struktúrák rendelkezhetnek ref módosítóval megjelölt adattagokkal, ami lehetővé teszi, hogy módosítható értékekkel dolgozzunk a referencia struktúránkban. Ennek jelentősége nagyon teljesítménykritikus kódok esetén van, mivel így elkerülhető, hogy az adattag módosítása másolatot eredményezzen a struktúrából.
Ez jól hangzik, de van egy hátránya is ennek, mégpedig az, hogy a ref módosítóval megjelölt adattagok rendelkezhetnek null értékkel, még akkor is, ha azok érték típusok.
using System;
namespace RefStructPelda
{
public ref struct MutablePoint
{
//ref módosítóval kell megjelölni, mivel referenciát adunk vissza
public ref int X => ref _x;
public ref int Y => ref _y;
//privát, mert ha publikus lenne
//akkor kikerülhetne a heap-re a boxing miatt
private ref int _x;
private ref int _y;
//referenciaként megy át érték
public MutablePoint(ref int x, ref int y)
{
//referencia szerinti érték átadás
_x = ref x;
_y = ref y;
}
}
internal static class Program
{
private static void Main(string[] args)
{
int x = 3;
int y = 4;
MutablePoint point = new MutablePoint(ref x, ref y);
ref int xRef = ref point.X;
ref int yRef = ref point.Y;
xRef = 5;
yRef = 6;
Console.WriteLine($"Updated Point: ({x}, {y})");
}
}
}
A program kimenete:
Updated Point: (5, 6)
A fenti példában a MutablePoint egy X és Y koordinátát fog össze referenciaként, de mivel referencia struktúra és az adattagjai is referenciák, valójában benne memória allokáció nem történik.
Ez megfigyelhető a főprogramban is: bemenetként 3 és 4 értéket adtunk át, majd a MutablePoint referencia mágiája után az eredeti x és y változó értékét módosítottuk úgy, hogy nem használtunk unsafe kontextust és pointereket.