Debugger i atrybuty, czyli jak usprawniać sobie codzienną pracę
Każdy programista pewną część swojego życia spędza z... debuggerem ;)
Chyba zgodzicie się ze mną, że jest to bardzo przydatne narzędzie. Gdy
pojawia się jakiś błąd i nie do końca wiemy co w trawie piszczy to
odpalamy go i linia po lini weryfikujemy nasz program. Czasami jednak
debugowanie może być uciążliwe... Pokażę kilka atrybutów, którymi
możemy podpowiadać debuggerowi.
Zaglądanie do getterów, które podajemy jako parametry wywołania metody.
Najczęstszym
problemem, który napotykam podczas debugowania to denerwujące
wskakiwanie debuggera do getterów właściwości, które przekazuję w
wywołaniu metody. Zwykle taki getter nie ma żadnej dodatkowej logiki
tylko zwraca wartość prywatnego pola. Mając metodę z kilkoma tak
podanymi parametrami łatwo się zgubić. Wiele razy w pośpiechu wciskałem
F10, żeby wyskoczyć z tego gettera i okazywało się, że nacisnąłem o
jeden raz za dużo i ominąłem wywołanie metody (sic!). Żeby było
śmieszniej zwykle zdarza mi się to przy ostatnim parametrze metody! Na
szczęście jest na to ratunek. W
System.Diagnostics znajduje się atrybut
DebuggerStepThroughAttribute,
który zaaplikowany do metody lub właściwości karze debuggerowi ominąć
oznaczony element i zatrzymać się po jego wykonaniu :) Zatem naszą
znienawidzoną właściwość wystarczy udekorować:
private string _myProperty;
public string MyProperty
{
[DebuggerStepThrough]
get { return _myProperty; }
}Tak oznaczonej właściwości już nie zobaczymy pod czas debugowania :D.
Przeglądanie zawartości złożonych obiektów
Czasami
budujemy złożone klasy. Mogą to być na przykład takie zawierające
wewnętrzne listy bądź też całkiem proste, z których budujemy większe
struktury (np. węzły drzewa). Debugując taką klasę mamy utrudnione
zadanie ponieważ musimy rozwijać różne elementy, żeby w końcu zobaczyć
co zawiera ta wewnętrzna lista, albo zaglądać do elementów wgłąb tak
jak to ma miejsce przy debugowaniu drzew. Przykładowe debugowanie
drzewa binarnego wygląda tak:

Jak
widać na załączonym obrazku wyciągnięcie wartości, które nas
rzeczywiście interesują, czyli wartości w poszczególnych węzłach, jest
trudne. Obraz jest zaciemniony przez inne pola. Istnieje jednak sposób,
aby ułatwić sobie życie - atrybut
DebuggerTypeProxyAttribute.
Za jego pomocą możemy określić proxy, które będzie wyświetlanie podczas
debugowania. Klasę proxy definiuje się bardzo łatwo. Może to być
dowolna klasa, która posiada konstruktor przyjmujący parametr typu
klasy, którą opatrzymy tym atrybutem. Podczas debugowania to klasa
proxy będzie wyświetlana, czyli zobaczymy to, co w niej zdefiniujemy. W
przykładzie z drzewem zdefiniowałem sobie taką klasę i dodałem do niej
publiczne pole
InOrder, które zwraca wartości drzewa właśnie w takim porządku:
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public string[] InOrder
{
get
{
List<String> result = new List<string>();
GetInOrder(this._node, result, "");
return result.ToArray();
}
}Należy tutaj zauważyć kolejny pomocny atrybut.
DebuggerBrowsableAtribute służy do podpowiadania debuggerowi jak wyświetlać dane pole. Do dyspozycji mamy trzy wartości:
Collapsed - element będzie zwinięty,
Never - element nie będzie pokazywany oraz
RootHidden -
element nie będzie pokazywany, ale zamiast niego zostaną wstawione jego
podelementy. W powyższym przykładzie ukryłem element InOrder, aby od
razu wyświetlały się elementy listy, którą zwraca. W taki oto sposób
debugowanie stało się o wiele prostsze:

Jak nie trudno zauważyć zawartość drzewa jest banalna do odczytania.
Wyświetlanie złożonych obiektów
Ostatnim atrybutem, o którym chciałem jeszcze wspomnieć jest
DebuggerDisplayAttribute,
który służy do definiowania napisu, który będzie wyświetlany dla
złożonego elementu (klasy). Aplikując go podajemy również napis, który
będzie wyświetlany. W tym napisie możemy odwoływać się do właściwości
obiektu poprzez {Property}:
[DebuggerDisplay("Val: {Value}, Desc: {Description}")]
class SimpleClass
{
private int _value;
public int Value {...}
private string _description;
public string Description {...}
public SimpleClass(int value, string description) {...}
}Dzięki niemu nasza klasa będzie widoczna w debuggerze w następujący sposób:

Przedstawiłem tu techniki, z których korzystam na co dzień. Może i wam się przydadzą. :)
W załączniku zamieściłem przykładowy program dla ciekawskich.