让调试器显示自定义 IList 类的内容
如何让调试器像对待 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)。
调试器将这个类像任何其他类一样显示
如果你想查看列表中的项目呢?在这种情况下,这很简单;只需查看 _list
内部即可。但是,在某些自定义 IList
类中,获取列表项目要困难得多。
解决方案
为了显示列表内容,你需要使用 .NET 框架本身使用的相同技巧。通过 Reflector,我了解到 List<T>
使用一对属性更改其外观:DebuggerTypeProxy
和 DebuggerDisplay
[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(); }
}