輔助自訂集合類別偵錯

  • 244
  • 0
  • 2024-02-28

這篇文章要介紹的是對於自訂集合類別的偵錯輔助類別。

建立一個簡單的自訂集合類別

這個集合類別的設計並不完整,也沒有實作一些相關的介面,單純只是為了介紹偵錯輔助類別而存在的:

public class MyCollection<T> 
{
    T[] _value;

    public int Count { get; set; }

    public int Capacity
    {
        get => _value.Length;
    }
    public MyCollection()
    {
        _value = new T[0];
    }

    public T this[int index]
    {
        get
        {
            if (index < 0 || index >= Count)
            {
                throw new IndexOutOfRangeException();
            }
            return _value[index];
        }

        set
        {
            if (index < 0 || index >= Count)
            {
                throw new IndexOutOfRangeException();
            }
            _value[index] = value;
        }
    }

    public void Add(T item)
    {
        if (Count == Capacity)
        {
            var newArray = new T[Capacity == 0 ? 4 : 2 * Capacity];
            for (int i = 0; i < Count; i++)
            {
                newArray[i] = _value[i];
            }
            _value = newArray;
        }
        _value[Count] = item;
        Count++;

    }    
}
和既有的 List<T> 在偵錯時期工具視窗資訊內容的比較

先建立一個簡單的實驗用程式碼:

 static void Main(string[] args)
 {
     var list = new List<int>() { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
     Console.WriteLine(list.Count);
     var collection = new MyCollection<int>();
     for (int i = 0; i < 10; i++)
     {
         collection.Add(i);
         
     }
     Console.WriteLine(collection.Count);
 }

把中斷點下在兩個 Console.WriteLine 的位置。

觀察這兩者的資訊內容有何不同?

(1) 在 List<int> 中斷的時候看到的資訊是這樣:

(2) 而在 MyCollection<int> 看到的資訊卻是這樣:

乍看好像沒甚麼不一樣,但仔細瞧瞧,在 List<int> 看到的是有效的資訊,也就是只有出現 Add 進來的10個元素 ;但在 MyCollection<int> 你看到的 _value 會有16個元素,但其實有效的元素是 10 個,這表示目前的偵錯資訊和期待是不相符的。

註:事實上 List<int> 裡的內部儲存陣列裡是16個元素,如果你真想看到,點下未經處理的檢視就有了。

建立輔助類別協助正確展現偵錯資訊
public class MyCollectionDebugView<T>
{
    private MyCollection<T> _collection;

    public MyCollectionDebugView(MyCollection<T> collection)
    {
        _collection = collection;
    }

    [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
    public T[] Items
    {
        get
        {
            T[] items = new T[_collection.Count];
            for (int i = 0; i < _collection.Count; i++)
            {
                items[i] = _collection[i];
            }
            return items;
        }
    }
}

這個類別主要是傳入要觀察資訊的 MyCollection<T> 執行個體,然後靠索引子列出資訊,在索引子上必須加入 DebuggerBrowsableAttribute,並且設定為 RootHidden 隱藏根元素 (不同的 DebuggerBrowsableState 會有不同效果,有興趣的朋友可以自己試試看)。

接著在 MyCollection<T> class 透過   DebuggerTypeProxyAttribute 套用這個 MyCollectionDebugView 。

[DebuggerTypeProxy(typeof (MyCollectionDebugView<>))]
public class MyCollection<T> 

現在能看到的資訊變成這樣:

好像少了點甚麼?在 List<T> 的偵錯資訊裡還多出顯示一個 Count 屬性,所以再補上一個 Attribute:

[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof (MyCollectionDebugView<>))]
public class MyCollection<T> 

這樣偵錯資訊的顯示就非常接近 List<T> 的樣子了。

以後如果有機會自訂集合型別,記得要幫它建立偵錯輔助類別和套用 Debugger 相關的 Attributes,這樣設計出來的型別的使用體驗會更好喔。

範例在此。