Debuggolás közben pár attribútum segÃtheti a dolgunkat. Összetett tÃpusok esetén a debugger a tÃpus ToString() metódusát meghÃvja és annak a segÃtségével jelenÃti meg a tÃpus értékét, azonban a ToString() csak ezért felüldefiniálása nem mindig lehetséges. Például ha már van egy ToString() implementációnk és az nem jelenÃt meg minden adatot, mert elsÅ‘sorban felhasználóknak szántuk az implementációnkat. Ebben az esetben a tÃpusunkat a DebuggerDisplay attribútummal annotálva eltérű működést érhetünk el.
A DebuggerDisplay konstruktorában egy szöveget kell megadnunk, amit meg szeretnénk jelenÃteni. A string interpolációhoz hasonlóan az adattagokra kapcsos zárójelek között hivatkozhatunk és akár metódusok eredményét ki tudjuk Ãratni. Nézzünk egy egyszerű példát:
[DebuggerDisplay("{DistanceFromZero()}")]
internal readonly struct Point
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
Y = y;
X = x;
}
public readonly double DistanceFromZero()
{
return Math.Sqrt((X * X) + (Y * Y));
}
}
A példában a DistanceFromZero metódus eredményét Ãratjuk ki hibakeresés közben Point osztály esetén. A kódban szerepel még egy hasznos attribútum, a DebuggerBrowsable, amivel az adattag megjelenését tudjuk befolyásolni. Ennek a konstruktorában egy DebuggerBrowsableState értéket kell megadnunk. Ennek értéke lehet Never, Collapsed vagy RootHidden. A Never teljesen elrejti a tulajdonságot. A Collapsed akkor hasznos, ha az adattag egy struktúra vagy egy osztály és annak az adattagjait alapértelmezetten összecsukott állapotban szeretnénk mutatni. A RootHidden érték kollekciók esetén hasznos, mivel elrejti a szülÅ‘t és csak az al elemeket elemeket mutatja a debugger.
A fenti kódrészlet eredménye az alábbi kimenetet eredményezni a debuggerben:
Ezen két attribútum igen hasznos tud lenni, de mi van akkor ha a megjelenÃteni kÃvánt adat egy másik osztályban van? Tételezzük fel, hogy az elÅ‘zÅ‘ példában bemutatott pont struktúra tényleg csak az X és Y értékeket tárolja, a DistanceFromZero() pedig az alkalmazás logikájának részeként máshol van implementálva. Ebben az esetben a DebuggerTypeProxy attribútummal megadhatunk egy tÃpust, amibÅ‘l a debugger egy példányt fog létrehozni és azon keresztül jelenÃti meg a tÃpusunkat. Nézzünk egy példát erre:
[DebuggerTypeProxy(typeof(PointDebugger))]
internal readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
Y = y;
X = x;
}
}
internal static class AppLogic
{
public static double DistanceFromZero(Point p)
{
return Math.Sqrt((p.X * p.X) + (p.Y * p.Y));
}
}
internal class PointDebugger
{
private readonly Point _point;
public PointDebugger(Point point)
{
_point = point;
}
public int X => _point.X;
public int Y => _point.Y;
public double DistanceFromZero
=> AppLogic.DistanceFromZero(_point);
}
A DebuggerTypeProxy konstruktorában megadott PointDebugger tÃpust fogja példányosÃtani a debugger. Az ilyen módon megadott osztályoknak egy paraméteres konstruktorral kell rendelkezniük, ami az annotált tÃpust fogadja paraméterül. Jelen esetben ez a Point struktúra. Ezt követÅ‘en a debugger ennek az osztálynak az adattagjait fogja mutatni az eredeti Point tÃpus helyett, mint ahogy az az alábbi ábrán is látható:
Jelen esetben az X és Y tulajdonságokat is továbbÃtotta a Proxy, de ez nem kötelezÅ‘, mivel a Raw view nézetben a proxy mögé tudunk látni debuggolás közben.