2009-06-12

DebuggerTypeProxyAttribute

Atrybut ten może zostać nałożony na klasę, strukturę i zestaw. 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. Atrybut ten mówi debugerowi by podczas debugowania danej klasy pokazał nam zawartość innej klasy. Jedynym wymaganiem względem tej klasy jest by istniał jej konstruktor przyjmujący jako parametr obiekt klasy, którą chcemy substytuować. Przykład zastosowania:
public class List
{
  public class Node
  {
      [System.Diagnostics.DebuggerBrowsable(
          System.Diagnostics.DebuggerBrowsableState.Never)]
      private Node m_next;

      public readonly int Value;

      public Node(int a_value)
      {
          Value = a_value;
      }

      public Node Next
      {
          get
          {
              return m_next;
          }
      }

      protected internal void Add(Node a_node)
      {
          if (m_next == null)
              m_next = a_node;
          else
              m_next.Add(a_node);
      }
  }

  [System.Diagnostics.DebuggerBrowsable(
      System.Diagnostics.DebuggerBrowsableState.Never)]
  private Node m_head;

  public void Add(Node a_node)
  {
      if (m_head == null)
          m_head = a_node;
      else
          m_head.Add(a_node);
  }

  public Node Head
  {
      get
      {
          return m_head;
      }
  }
}

public class DebuggerTypeProxyTest
{
  public void Test()
  {
      List list = new List();

      list.Add(new List.Node(5));
      list.Add(new List.Node(6));
      list.Add(new List.Node(7));

      list.ToString();
  }
}
Podgląd list wygląda tak: Po uzupełnieniu kodu odpowiednio:
public class ListProxy
{
  private List m_list;

  public ListProxy(List a_list)
  {
      m_list = a_list;
  }

  private IEnumerable<int> getEnum()
  {
      List.Node node = m_list.Head;

      while (node != null)
      {
          List.Node n = node;
          node = node.Next;
          yield return n.Value;
      }
  }

  [System.Diagnostics.DebuggerBrowsable(
      System.Diagnostics.DebuggerBrowsableState.RootHidden)]
  public int[] List
  {
      get
      {
          return getEnum().ToArray();
      }
  }
}

[System.Diagnostics.DebuggerTypeProxy(typeof(ListProxy))]
public class List
{
  public class Node
  {
      [System.Diagnostics.DebuggerBrowsable(
          System.Diagnostics.DebuggerBrowsableState.Never)]
      private Node m_next;

      public readonly int Value;

      public Node(int a_value)
      {
          Value = a_value;
      }

      public Node Next
      {
          get
          {
              return m_next;
          }
      }

      protected internal void Add(Node a_node)
      {
          if (m_next == null)
              m_next = a_node;
          else
              m_next.Add(a_node);
      }
  }

  [System.Diagnostics.DebuggerBrowsable(
      System.Diagnostics.DebuggerBrowsableState.Never)]
  private Node m_head;

  public void Add(Node a_node)
  {
      if (m_head == null)
          m_head = a_node;
      else
          m_head.Add(a_node);
  }

  public Node Head
  {
      get
      {
          return m_head;
      }
  }
}

public class DebuggerTypeProxyTest
{
  public void Test()
  {
      List list = new List();

      list.Add(new List.Node(5));
      list.Add(new List.Node(6));
      list.Add(new List.Node(7));

      list.ToString();
  }
}
W debugerze zobaczymy: Dzięki użyciu tego atrybutu możemy kompleksowo zmienić sposób pokazywania zawartości klasy podczas debugowania. Co ważniejsze dane możemy wizualizować zupełnie inaczej niż wyglądają w strukturze obiektu. Jeśli obiekt implementuje strukturę listę (jak w powyższym przykładzie), możemy je pokazać jako tablicę, nie zmuszając użytkownika do klikania poprzez wszystkie elementy listy. Możemy zmienić wizualizację naszych klas, a także klas z biblioteki .NET (jak i dowolnie innej biblioteki). Możemy też powiedzieć, że atrybut ten pozwala nam rozdzielić kod dla debugera od kodu klasy. Jeśli klasa proxy musi się dostać do składników prywatnych klasy debugowanej najlepiej zdefiniować ją jako klasę wewnętrzną klasy debugowanej. Reszta informacji dla tego atrybutu podaje w postaci pytań i odpowiedzi. Jak można się przekonać niżej czasami coś nie działa, czasami działa inaczej niż powinno. Chyba wszystkie te sytuacje podane niżej kiedy coś nie działa, a powinno, można zakwalifikować jako błędy. Jak nałożyć atrybut na zestaw ? Podobnie jak dla DebugerDisplayAttribute. Więcej szczegółów można znaleźć w tamtym wpisie. Przykład:
[assembly: System.Diagnostics.DebuggerTypeProxy(typeof(TestAppNET.Priv.TestClassProxy), Target = typeof(TestAppNET.TestClass))]
Czy klasa proxy może być klasą wewnętrzną ? Może. Czy klasa proxy może zostać zastosowana dla klasy debugowanej generycznej? Tak. Przykład:
public class TestClassProxy<K,V>
{
  public K x;
  public V y;
  public string str;

  public TestClassProxy(TestClass<K,V> a_tc)
  {
      x = a_tc.x;
      y = a_tc.y;
      str = "test " + a_tc.str;
  }
}

[System.Diagnostics.DebuggerTypeProxy(typeof(TestAppNET.TestClassProxy<,>))]
public class TestClass<K,V>
{
  public K x;
  public V y;
  public string str;
}
Dobrze jest zwrócić uwagę jak definiuje się typ generyczny w atrybucie DebuggerTypeProxy. Teraz jeśli zamiast typu w atrybucie podamy stringa przykład przestanie działać:
[System.Diagnostics.DebuggerTypeProxy("TestAppNET.TestClassProxy<,>")]
Proxy przestanie działać też dla poniższego przykładu kiedy zamkniemy typ generyczny dla proxy:
public class TestClassProxy
{
  public int x;
  public string y;
  public string str;

  public TestClassProxy(TestClass a_tc)
  {
      x = a_tc.x;
      y = a_tc.y;
      str = "test " + a_tc.str;
  }
}

public class BaseClass<K, V>
{
  public K x;
  public V y;
}

[System.Diagnostics.DebuggerTypeProxy("TestAppNET.TestClassProxy")]
public class TestClass : BaseClass<int, string>
{
  public string str;
}
Jak musi być widoczność klasy proxy ? Nie ma znaczenia. Oczywiście podając klasy debugowaną jako type musi ona być widoczna. Podając ją jako string należy zwrócić uwagę, że jeśli klasa proxy jest klasą wewnętrzną w jej pełnej nazwie dla składników klasy wewnętrznej zamiast kropki używamy plusa:
[System.Diagnostics.DebuggerTypeProxy("TestAppNET.SomeClass+InnerTestClassProxy")]
Dla atrybutu dla zestawu kiedy zamiast typu użyłem stringa proxy przestało działać (niezależnie dla którego parametru konstruktora atrybutu). Jaka musi być widoczność klasy debugowanej przy definiowaniu atrybutu dla zestawu ? Zgodnie z tym co powiedziałem w powyżej musi być widoczna, by móc ją podać jako typ. Czy klasa proxy może dziedziczyć po innej klasie, implementować interfejsy ? Nie ma problemu. Czy jeśli klasa proxy dziedziczy po innej klasie to czy jej pola i właściwości też są pokazywane podczas debugowania ? Tak, w rozwinięciu klasy. Co jeśli proxy nałożymy na klasę bazową klasy, której podgląd będziemy chcieli zobaczyć ? Proxy dla tej klasy zostanie uruchomione. Co jeśli konstruktor proxy jako parametru wymaga klasy bazowej naszej debugowanej klasy lub interfejsu, który nasza klasa debugowana implementuje ? Jeśli takie proxy jest nałożone na naszą klasę, podczas debugowania klasy proxy zostanie użyta. Czy możemy nasza klasę proxy wykorzystać dla dwóch różnych klas debugowanych ? Czyli nasza klasa proxy posiada dwa konstruktory dla dwóch różnych klas debugowanych. Możemy tak zrobić. Czy składniki prywatne klasy proxy są wyświetlane ? Nie, pokazywane są tylko składniki publiczne i protected (pola i właściwości). Co się stanie kiedy wpis w atrybucie DebuggerTypeProxyAttribute jest błędny ? Nic się nie dzieje, nie ma żadnego komunikatu o błędzie. Czy po rekompilacji kodu w którym znajduje się klasa proxy, by efekt był widoczny, a atrybut został nałożony na zestaw, trzeba zrestartować VS ? Nie. Dla ścisłości atrybut na zestaw został już nałożony, a VS zrestartowane, teraz tylko rekompilujemy klasę proxy. Czy klasa proxy i klasa debugowana mogą się znajdować w innych zestawach ? Mogą, ale aby uniknąć circular references, w innym zestawie może się znajdować tylko debugowana klasa. Po umieszczeniu tam proxy kodu nie uda nam się tego skompilować. Co jeśli dla klasy debugowanej są ustanowione jednocześnie proxy w zestawie i klasie debugowanej ? W przeciwieństwie do tego jak działa atrybut DebugerDisplayAttribute tutaj proxy zdefiniowane w klasie debugowanej zostanie zignorowane. Co jeśli zestaw z klasa proxy załadujemy dynamicznie ? Załadować się załadował, ale debug nie uwzględnił klasy proxy. Co jeśli klasa debugowana jest w innym zestawie ? Nie ma żadnego problemu o ile nie jest ładowana dynamicznie. Przy ładowaniu dynamicznym klasa proxy się nie skompiluje z powodu braku referencji do klasy debugowanej. Patrz pytanie wyżej (klasa proxy nie może być ładowana dynamicznie). Jak debugować klasę proxy ? Można wyświetlać okienka, wydawać dźwięki, logować do pliku. Nie udało mi się skorzystać z konsoli VS dostępnej w trybie debug. Kiedy tworzony jest obiekt klasy proxy ? Dla Watch, Autos, Locals dla każdego wymaganego odświeżenia wartości (krok debugera albo zatrzymanie wykonywania programu), o ile jest rozwinięty. Dla podglądy stworzenie obiektu proxy następuje w momencie rozwinięcia obiektu. Ponieważ obiekt klasy proxy tworzony jest za każdym razem należy zwrócić uwagę na szybkość działania klasy proxy. Co się dzieje kiedy na klasę proxy nałożymy jakieś atrybuty modyfikujące wyświetlanie zawartości klasy w debugerze ? Dla klasy proxy DebugerDisplayAttribute nie jest uwzględniany. To samo tyczy się zastosowania metody ToString() do pokazywania informacji o obiekcie podczas debugowania. Więcej informacji można znaleźć przy opisie atrybutu DebugerDisplayAttribute. Atrybut DebuggerBrowsable działa.

Brak komentarzy:

Prześlij komentarz