Suggestion: Show runtime type when dumping collections of polymorphic types.
Dump() is a great way to view your data, but it gets confusing when dealing with collections of polymorphic types. You'll see the expected columns of the runtime data dumped, but many times, the column does not correspond to the displayed type in the header. Especially confusing when dumping System.Linq.Expressions or exceptions.
e.g.,
Exception[] errors = [
new ArgumentNullException("paramName", "ArgumentNullException message"),
new System.IO.FileNotFoundException("FileNotFoundException message", "filename"),
new System.ComponentModel.Win32Exception(123, "Win32Exception message"),
];
errors.Dump();

When dealing with collections of mixed types, could the runtime type be displayed in a column or something?
I believe this would make these dumps much more useful. Could probably do some analysis beforehand to determine if it's even needed (like if all items are the same type, it's unnecessary). Also, make it very clear when cells don't apply to a column.
something like:
Perhaps, the header could include the types it contains so it's not as information dense?
Comments
-
On second thought, knowing the runtime type on even homogenous collections is far more useful so conditionally not showing it doesn't look great:

What exception am I looking at?Other things to consider:
This certainly should not be displayed always, especially for small sets of type hierarchies. Maybe set up a whitelist of declared base types to show the runtime types by default that we may manage? (e.g., object, Exception, Expression) Also have options to also include other derived types for those in the whiltelist. Maybe if the declared type is abstract, show by default? -
I was able to implement it in a static
ToDump()method, only issue is that the header doesn't match the original type and I'm not sure if it's possible through here.static object? ToDump(object? obj) { if (obj is IEnumerable e && obj.GetType() is {} type && type.GetInterface("IEnumerable`1") is { } inter && whitelist.Contains(inter.GetGenericArguments().Single())) { var g0 = Type.MakeGenericMethodParameter(0); var g1 = Type.MakeGenericMethodParameter(1); var m = typeof(Enumerable).GetMethod("Select", 2, BindingFlags.Public | BindingFlags.Static, [ typeof(IEnumerable<>).MakeGenericType(g0), typeof(Func<,>).MakeGenericType(g0, g1) ]); var itemType = inter.GetGenericArguments().Single(); var method = m!.MakeGenericMethod(itemType, typeof(object)); return method.Invoke(null, [e, IncludeRuntimeType]); } return obj; [return:NotNullIfNotNull(nameof(obj))] static object? IncludeRuntimeType(object? obj) { if (obj is null) return obj; var res = new ExpandoObject() as IDictionary<string, object?>; res["(Runtime Type)"] = Util.Metatext($"({obj.GetType().Name})"); foreach (var prop in Util.ToExpando(obj)) res.Add(prop); return res; } } -
How about this:
void Main() { Exception[] errors = [ new ArgumentNullException("paramName", "ArgumentNullException message"), new System.IO.FileNotFoundException("FileNotFoundException message", "filename"), new System.ComponentModel.Win32Exception(123, "Win32Exception message"), ]; errors.WithType().Dump(); } static class Extensions { public static object WithType (this object obj) => Util.Merge (Util.OnDemand (obj?.GetType().Name, () => obj?.GetType()), obj); public static IEnumerable WithType<T> (this IEnumerable<T> collection) => collection.Select (item => item.WithType()); }
