2009-06-02

System.Diagnostic.DebuggerBrowsableAttribute

Za pomocą tego atrybutu kontrolujemy jak środowisko debugujące (Visual Studio) wyświetla nam informacje o stanie zmiennych (pól i właściwości) podczas zatrzymanego wykonywania programu. Reguły definiowane przez ten atrybut dotyczą zarówno okienek wyskakujących po najechaniu na zmienną, jak i tego co pojawia się w Watch, Autos, Locals.
public class TestClass1
{
    [System.Diagnostics.DebuggerBrowsable (System.Diagnostics. DebuggerBrowsableState.Collapsed)]
    public DateTime dt1 = DateTime.Now;

    [System.Diagnostics.DebuggerBrowsable (System.Diagnostics. DebuggerBrowsableState.Never)]
    public DateTime dt2 = DateTime.Now;

    public DateTime dt3 = DateTime.Now;
}


public class TestClass2
{
    private int y;

    [System.Diagnostics.DebuggerBrowsable (System.Diagnostics. DebuggerBrowsableState.Never)]
    private int d;

    [System.Diagnostics.DebuggerBrowsable (System.Diagnostics. DebuggerBrowsableState.Never)]
    private int e;

    public int D
    {
        get
        {
            return d;
        }
        set
        {
            d = value;
        }
    }

    public int E
    {
        get
        {
            y = System.DateTime.Now.Millisecond;
            return e;
        }
        set
        {
            e = value;
        }
    }

    public int X
    {
        get;
        set;
    }
}

public class TestClass3
{
    [System.Diagnostics.DebuggerBrowsable (System.Diagnostics. DebuggerBrowsableState.RootHidden)]
    public DateTime dt1 = DateTime.Now;

    public int X;
}

public class TestClass4
{
    [System.Diagnostics.DebuggerBrowsable (System.Diagnostics. DebuggerBrowsableState.RootHidden)]
    public DateTime dt1 = DateTime.Now;


    [System.Diagnostics.DebuggerBrowsable (System.Diagnostics. DebuggerBrowsableState.RootHidden)]
    public DateTime dt2 = DateTime.Now;

    public int X;
}

public class TestClass5 : IList<int>
{
    [System.Diagnostics.DebuggerBrowsable (System.Diagnostics. DebuggerBrowsableState.RootHidden)]
    private int[] Collection
    {
        get
        {
            return new int[] { 1, 2, 3, 4 };
        }
    }

    public int IndexOf(int item)
    {
        throw new NotImplementedException();
    }

    public void Insert(int index, int item)
    {
        throw new NotImplementedException();
    }

    public void RemoveAt(int index)
    {
        throw new NotImplementedException();
    }

    public int this[int index]
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public void Add(int item)
    {
        throw new NotImplementedException();
    }

    public void Clear()
    {
        throw new NotImplementedException();
    }

    public bool Contains(int item)
    {
        throw new NotImplementedException();
    }

    public void CopyTo(int[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public int Count
    {
        get 
        {
            return Collection.Length;
        }
    }

    public bool IsReadOnly
    {
        get 
        {
            return true;
        }
    }

    public bool Remove(int item)
    {
        throw new NotImplementedException();
    }

    public IEnumerator<int> GetEnumerator()
    {
        return Collection.Cast<int>().GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public partial class Form1 : Form
{
    public void Test()
    {
        TestClass1 tc1 = new TestClass1();
        TestClass2 tc2 = new TestClass2();
        TestClass3 tc3 = new TestClass3();
        TestClass4 tc4 = new TestClass4();
        TestClass5 tc5 = new TestClass5();

        tc1.ToString();
        tc2.ToString();
        tc3.ToString();
        tc4.ToString();
        tc5.ToString();
    }

    public Form1()
    {
        InitializeComponent();

        Test();
    }
}
Zobaczmy jak debuger wyświetla informacje o poszczególnych zmiennych (breakpoint jest ustawiony w metodzie Test() po stworzeniu zmiennych lokalnych. Poniżej zrzuty ekranu z zawartości Watch-a:
Konstruktor atrybutu System.Diagnostics.DebuggerBrowsable przyjmuje jako parametr wartość enumeracji System.Diagnostics.DebuggerBrowsableState. W kodzie obiektu TestClass1 pokazano zastosowanie dwóch z nich. Pole dt1 zostało oznaczone jako Collapsed. Jest to domyślne zachowanie debugera i tak naprawdę sposób pokazywania zmiennej dt1 będzie taki sam, jak sposób pokazywania zmiennej dt3. Pole dt2 zostało oznaczone jako Hidden i tym samym debuger go nie pokazuje. Ale jak możemy zauważyć na pierwszym zrzucie nic nie stoi na przeszkodzie by wymusić pokazywanie pola ukrytego. W TestClass2 widzimy typowe zastosowanie Hidden, kiedy chcemy ukryć dublujące się wartości pół i odpowiadających im właściwości, by pokazywana informacja była bardziej czytelna. Ogólnie powinniśmy ukryć to co jest nieistotne z punkty widzenia programisty korzystającego z obiektu. Nie ma to takiego znaczenia jeśli piszemy dla siebie, ale jeśli tworzymy jakiś komponent, który może być używany przez innych, to warto sprawić, by nie musieli się oni przejmować debugując, nieistotnymi polami. Jest tutaj pewna analogia do deklarowania widoczności jako private i protected. W przykładzie tym możemy zauważyć także pewną pułapkę. Za każdym razem gdy najedziemy na zmienną tc2 to zobaczymy inną wartość zmiennej y. Debuger wyświetlając właściwości wywołuje ich metody get, jeśli kod tam zawarty w jakikolwiek sposób zmienia obiekt, to możemy mieć problem. Warto o tym pamiętać. Jeśli podczas ewaluacji właściwości zostanie wyrzucony wyjątek to zostaniemy o tym poinformowani: Pozostała nam do omówienia ostatnia wartość enumeracji System.Diagnostics.DebuggerBrowsableState RootHidden. Na przykładzie TestClass3 i TestClass4 widzimy, że wszystkie właściwości i pola klasy DateTime stają się właściwościami i polami klasy zawierającej. Jednak przykłady te nie ilustrują korzyści jakie atrybut tego typu nam daje. Przykład z TestClass4 jest już ekstremalnie głupi. Sens RootHidden widać w TestClass5 kiedy oznaczona tym atrybutem zmienna implementuje interfejs IEnumerable. Kolekcja znajdująca się pod właściwością Collection staje się jakby elementem klasy zawierającej. Jest to szczególnie przydatne kiedy implementujemy klasę, która jest pojemnikiem na inne obiekty, przy czym wcale nie musimy tutaj implementować interfejsu jakiejś kolekcji. I ostatni przykład TestClass6:
public class TestClass6
{
    [System.Diagnostics.DebuggerBrowsable (System.Diagnostics. DebuggerBrowsableState.RootHidden)]
    private List<int> Collection
    {
        get
        {
            return new List<int> { 1, 2, 3, 4 };
        }
    }
}
Widok w Watch-u: Widzimy, że pojawił się element drzewa Raw View, w którym dodatkowo możemy się zapoznać z ukrytymi elementami List<int>.

Brak komentarzy:

Prześlij komentarz