Click here to Skip to main content
15,886,757 members
Articles / Programming Languages / C#
Article

Make the debugger show the contents of your custom IList class

Rate me:
Please Sign up or sign in to vote.
4.67/5 (15 votes)
6 Aug 2008MIT2 min read 44.8K   27   4
How to make the debugger give the same special treatment to your custom IList as it gives to List.

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:

C#
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:

before.png

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:

C#
[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:

C#
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:

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

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:

C#
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(); }
}

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer None
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, the Enhanced C# programming language (in progress), the parser generator LLLPG, and LES, a syntax to help you start building programming languages, DSLs or build systems.

My overall focus is on the Language of your choice (Loyc) initiative, which is about investigating ways to improve interoperability between programming languages and putting more power in the hands of developers. I'm also seeking employment.

Comments and Discussions

 
GeneralSimpler.. but not as clever way of achieving more-or-less the same Pin
PhilWelch18-Dec-08 5:18
PhilWelch18-Dec-08 5:18 
GeneralRe: Simpler.. but not as clever way of achieving more-or-less the same Pin
Qwertie6-Nov-09 13:03
Qwertie6-Nov-09 13:03 
GeneralWhere is code example Pin
Vikas Misra(TCS)2-Sep-08 20:32
Vikas Misra(TCS)2-Sep-08 20:32 
GeneralRe: Where is code example Pin
Qwertie8-Sep-08 8:22
Qwertie8-Sep-08 8:22 
Here is a complete example:
C#
using System;
using System.Collections.Generic;
using System.Diagnostics;

[Serializable, DebuggerTypeProxy(typeof(CollectionDebugView<> )), 
               DebuggerDisplay("Count = {Count}")]
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(); }
}

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;
        }
    }
}

public class Program
{
	static void Main(string[] args)
	{
		SafeList<int> customList = new SafeList<int>(
			new int[] { 1, 2, 4, 8 });
		Console.WriteLine("Set a breakpoint on this line," +
			" and look at customList in the debugger.");
	}
}
</int></int></t></t></t></t></t></t></t></t>

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.