65.9K
CodeProject 正在变化。 阅读更多。
Home

让调试器显示自定义 IList 类的内容

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (15投票s)

2008年8月6日

MIT

2分钟阅读

viewsIcon

46184

如何让调试器像对待 List 一样对待你的自定义 IList。

引言

当你编写一个实现 IList<T> 的类时,调试器会像对待任何其他类一样显示你的类的成员。不幸的是,它不会显示列表的内容,而用户可能对此感兴趣。例如,假设你正在使用像这样的一种装饰器

public class SafeList<T> : IList<T>
{
    IList<T> _list;

    public SafeList(IList<T> innerList) { _list = innerList; }

    public T this[int index]
    {
        get { 
            if ((uint)index >= (uint)Count)
                return default(T);
            return _list[index];
        }
        set { _list[index] = value; }
    }
    public void CopyTo(T[] array, int arrayIndex) 
        { _list.CopyTo(array, arrayIndex); }
    public int Count { get { return _list.Count; } }
    public int IndexOf(T item) { return _list.IndexOf(item); }
    public void Insert(int index, T item) { _list.Insert(index, item); }
    public void RemoveAt(int index) { _list.RemoveAt(index); }
    public void Add(T item) { _list.Add(item); }
    public void Clear() { _list.Clear(); }
    public bool Contains(T item) { return _list.Contains(item); }
    public bool IsReadOnly { get { return _list.IsReadOnly; } }
    public bool Remove(T item) { return _list.Remove(item); }
    public IEnumerator<T> GetEnumerator() { return _list.GetEnumerator(); }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
        { return _list.GetEnumerator(); }
}

SafeList<T> 就像一个普通的列表,除了越界数组索引是合法的,并且返回类型 T 的默认值(例如,null 或 0)。

调试器将这个类像任何其他类一样显示

before.png

如果你想查看列表中的项目呢?在这种情况下,这很简单;只需查看 _list 内部即可。但是,在某些自定义 IList 类中,获取列表项目要困难得多。

解决方案

为了显示列表内容,你需要使用 .NET 框架本身使用的相同技巧。通过 Reflector,我了解到 List<T> 使用一对属性更改其外观:DebuggerTypeProxyDebuggerDisplay

[Serializable, DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>)), 
               DebuggerDisplay("Count = {Count}")]
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, 
                       IList, ICollection, IEnumerable

这个 DebuggerTypeProxy 属性导致调试器创建一个类型为 Mscorlib_CollectionDebugView<T> 的包装器,并显示该包装器而不是原始的 List<T>DebuggerDisplay 用于更改列表的“摘要视图”,以显示列表中有多少个项目。不幸的是,你不能直接使用 Mscorlib_CollectionDebugView,因为它被标记为 internal。但是,制作你自己的版本很容易

public class CollectionDebugView<T>
{
    private ICollection<T> collection;

    public CollectionDebugView(ICollection<T> collection)
    {
        if (collection == null)
            throw new ArgumentNullException("collection");
        this.collection = collection;
    }

    [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
    public T[] Items
    {
        get {
            T[] array = new T[this.collection.Count];
            this.collection.CopyTo(array, 0);
            return array;
        }
    }
}

现在,将以下属性添加到你的自定义列表中

[Serializable, DebuggerTypeProxy(typeof(CollectionDebugView<>)), 
               DebuggerDisplay("Count = {Count}")]
public class SafeList<T> : IList<T> // your custom list class
...

出于某种奇怪的原因,CollectionDebugView<> 必须在没有任何类型参数的情况下指定。

现在,调试器显示列表内容!

还有一个问题

如果你的类实现 IList<T>,则上述解决方案有效。但是,如果你使用特定的类代替“T”,例如“float”,则上述方法不再有效。即使你将 CollectionDebugView<> 更改为 CollectionDebugView<float>,它仍然不起作用。解决方案是手动编写 CollectionDebugView 的自定义版本。下面显示了一个示例

public class CollectionDebugViewFloat
{
    private ICollection<float> collection;

    public CollectionDebugViewFloat(ICollection<float> collection)
    {
        if (collection == null)
            throw new ArgumentNullException("collection");
        this.collection = collection;
    }

    [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
    public float[] Items
    {
        get {
            float[] array = new float[this.collection.Count];
            this.collection.CopyTo(array, 0);
            return array;
        }
    }
}

// This (useless) class represents a list of numbers from 0 to 9.
// It shows how to use CollectionDebugViewFloat.
[Serializable, DebuggerTypeProxy(typeof(CollectionDebugViewFloat)), 
               DebuggerDisplay("Count = {Count}")]
public class ZeroToNine : IList<float>
{
    public float this[int index]
    {
        get { return (float)index; }
        set { throw new NotImplementedException(); }
    }
    public void CopyTo(float[] array, int arrayIndex)
    {
        for (int i = 0; i < Count; i++)
            array[arrayIndex + i] = this[i];
    }
    public int Count { get { return 10; } }
    public int IndexOf(float item) { throw new NotImplementedException(); }
    public void Insert(int index, float item) { throw new NotImplementedException(); }
    public void RemoveAt(int index) { throw new NotImplementedException(); }
    public void Add(float item) { throw new NotImplementedException(); }
    public void Clear() { throw new NotImplementedException(); }
    public bool Contains(float item) { throw new NotImplementedException(); }
    public bool IsReadOnly { get { return true; } }
    public bool Remove(float item) { throw new NotImplementedException(); }
    public IEnumerator<float> GetEnumerator() { throw new NotImplementedException(); }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        { throw new NotImplementedException(); }
}
© . All rights reserved.