Introduction
When you write a class that implements IList<T>
, the debugger shows the members of your class just as it would any other class. Unfortunately, it does not show the contents of the list, which the user might be interested in. For example, suppose you are using a decorator like this one:
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>
is like a normal list, except that out-of-bounds array indexes are legal, and return the default value of type T
(e.g., null
or 0).
The debugger shows this class like any other:
What if you would rather see the items in the list? In this case, it's pretty easy; just look inside _list
. But, in some custom IList
classes, it is much harder to get at the list items.
The solution
To show the list contents instead, you need to use the same trick as the .NET framework itself. With Reflector, I learned that List<T>
changes its appearance using a pair of attributes: DebuggerTypeProxy
and DebuggerDisplay
:
[Serializable, DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>)),
DebuggerDisplay("Count = {Count}")]
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>,
IList, ICollection, IEnumerable
This DebuggerTypeProxy
attribute causes the debugger to create a wrapper of type Mscorlib_CollectionDebugView<T>
and show the wrapper instead of the original List<T>
. DebuggerDisplay
is used to change the "summary view" of the list to show how many items are in the list. Unfortunately, you can't use Mscorlib_CollectionDebugView
directly because it is marked internal
. However, it's fairly easy to make your own version of it:
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;
}
}
}
Now, add the following attributes to your custom list:
[Serializable, DebuggerTypeProxy(typeof(CollectionDebugView<>)),
DebuggerDisplay("Count = {Count}")]
public class SafeList<T> : IList<T>
...
For some strange reason, CollectionDebugView<>
must be specified without any type arguments.
Now, the debugger shows the list contents!
One more problem
The above solution works if your class implements IList<T>
. However, if instead of "T
", you use a specific class, such as "float
", the above no longer works. Even if you change CollectionDebugView<>
to CollectionDebugView<float>
, it still doesn't work. The solution is to manually write a customized version of CollectionDebugView
. An example is shown below:
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;
}
}
}
[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(); }
}