Click here to Skip to main content
11,479,486 members (54,041 online)
Click here to Skip to main content

Make the debugger show the contents of your custom IList class

, 6 Aug 2008 MIT 21.7K 25
Rate this:
Please Sign up or sign in to vote.
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:

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:

[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> // 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:

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

Share

About the Author

Qwertie
Software Developer Trapeze Software, Inc.
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, and LLLPG, among other things. Now I'm old.

In my spare time I'm developing a system called Loyc (Language of your choice), which will include an enhanced C# compiler. Many programs have an add-in architecture; why not your programming language? I'm also looking for a life partner. Oh hi future wife! Wazzap.

Comments and Discussions

 
GeneralSimpler.. but not as clever way of achieving more-or-less the same Pin
PhilWelch18-Dec-08 6:18
memberPhilWelch18-Dec-08 6:18 
GeneralRe: Simpler.. but not as clever way of achieving more-or-less the same Pin
Qwertie6-Nov-09 14:03
memberQwertie6-Nov-09 14:03 
GeneralWhere is code example Pin
Member 15911272-Sep-08 21:32
memberMember 15911272-Sep-08 21:32 
GeneralRe: Where is code example Pin
Qwertie8-Sep-08 9:22
memberQwertie8-Sep-08 9:22 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150520.1 | Last Updated 6 Aug 2008
Article Copyright 2008 by Qwertie
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid