Click here to Skip to main content
12,398,225 members (57,783 online)
Click here to Skip to main content
Add your own
alternative version

Stats

25.3K views
27 bookmarked
Posted

Make the debugger show the contents of your custom IList class

, 6 Aug 2008 MIT
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 None
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, LLLPG, and LES: XML for code, among other things. Now I'm old.

In my spare time I work on the Language of your choice (Loyc) initiative, which is about investigating ways to improve interoperability between programming languages, and includes Enhanced C# and LeMP, its Lexical Macro Processor.

You may also be interested in...

Comments and Discussions

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

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.

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