See the question and my original answer on StackOverflow

Unfortunately, the logic is pretty hardcoded in the CollectionEditor.GetDisplayText Method. It's not documented, but you can disassemble it with a tool. This is the code:

protected virtual string GetDisplayText(object value)
{
    string str;
    if (value == null)
        return string.Empty;

    // use the Name property
    PropertyDescriptor defaultProperty = TypeDescriptor.GetProperties(value)["Name"];
    if ((defaultProperty != null) && (defaultProperty.PropertyType == typeof(string)))
    {
        str = (string) defaultProperty.GetValue(value);
        if ((str != null) && (str.Length > 0))
        {
            return str;
        }
    }

    // or use the DefaultPropertyAttribute
    defaultProperty = TypeDescriptor.GetDefaultProperty(this.CollectionType);
    if ((defaultProperty != null) && (defaultProperty.PropertyType == typeof(string)))
    {
        str = (string) defaultProperty.GetValue(value);
        if ((str != null) && (str.Length > 0))
        {
            return str;
        }
    }

    // or use the TypeConverter
    str = TypeDescriptor.GetConverter(value).ConvertToString(value);
    if ((str != null) && (str.Length != 0))
    {
        return str;
    }

    // or use the type name
    return value.GetType().Name;
}

This code is pretty nasty because it does things basically the other way around. It should use the Name property as the last resort instead of focusing on it...

But all hope is not lost since the CollectionEditor class is not sealed. This is how you can fix it:

1) declare the EditorAttribute on the class holding the collection, something like this:

public class ClassA
{
    [Editor(typeof(MyCollectionEditor), typeof(UITypeEditor))]
    public List<ClassB> List { get; set; }
}

2) define you custom collection editor, like this;

public class MyCollectionEditor : CollectionEditor // needs a reference to System.Design
{
    public MyCollectionEditor(Type type)
        : base(type)
    {
    }

    protected override string GetDisplayText(object value)
    {
        // force ToString() usage, but
        // you also could implement some custom logic here
        return string.Format("{0}", value);
    }
}