I want a function that I can call as an alternative to .ToString(), that will show the contents of collections.
I've tried this:
public static string dump(Object o) {
if (o == null) return "null";
return o.ToString();
}
public static string dump<K, V>(KeyValuePair<K, V> kv) {
return dump(kv.Key) + "=>" + dump(kv.Value);
}
public static string dump<T>(IEnumerable<T> list) {
StringBuilder result = new StringBuilder("{");
foreach(T t in list) {
result.Append(dump(t));
result.Append(", ");
}
result.Append("}");
return result.ToString();
}
but the second overload never gets called. For example:
List<string> list = new List<string>();
list.Add("polo");
Dictionary<int, List<string>> dict;
dict.Add(1, list);
Console.WriteLine(dump(dict));
I'm expecting this output:
{1=>{"polo", }, }
What actually happens is this:
dict is correctly interpreted as an IEnumerable<KeyValuePair<int, List<string>>>, so the 3rd overload is called.
the 3rd overload calls dump on a KeyValuePair>. This should(?) invoke the second overload, but it doesn't -- it calls the first overload instead.
So we get this output:
{[1=>System.Collections.Generic.List`1[System.String]], }
which is built from KeyValuePair's .ToString() method.
Why isn't the second overload called? It seems to me that the runtime should have all the information it needs to identify a KeyValuePair with full generic arguments and call that one.
Generics is a compile time concept, not run time. In other words the type parametes are resolved at compile time.
In your foreach you call dump(t) and t is of type T.
But there is nothing known about T at this point other than that it is an Object.
That's why the first overload is called.
(updated) As mentioned in other answers, the problem is that the compiler does not know that type V is actually a List<string>, so it just goes to dump(object).
A possible workaround might be to check types at run time. Type.IsGenericType will tell you if the type of a variable has generics or not, and Type.GetGenericArguments will give you the actual type of those generics.
So you can write a single dump method receiving an object and ignoring any generics info. Note that I use the System.Collections.IEnumerable interface rather than System.Collections.Generics.IEnumerable<T>.
public static string dump(Object o)
{
Type type = o.GetType();
// if it's a generic, check if it's a collection or keyvaluepair
if (type.IsGenericType) {
// a collection? iterate items
if (o is System.Collections.IEnumerable) {
StringBuilder result = new StringBuilder("{");
foreach (var i in (o as System.Collections.IEnumerable)) {
result.Append(dump(i));
result.Append(", ");
}
result.Append("}");
return result.ToString();
// a keyvaluepair? show key => value
} else if (type.GetGenericArguments().Length == 2 &&
type.FullName.StartsWith("System.Collections.Generic.KeyValuePair")) {
StringBuilder result = new StringBuilder();
result.Append(dump(type.GetProperty("Key").GetValue(o, null)));
result.Append(" => ");
result.Append(dump(type.GetProperty("Value").GetValue(o, null)));
return result.ToString();
}
}
// arbitrary generic or not generic
return o.ToString();
}
That is: a) a collection is iterated, b) a keyvaluepair shows key => value, c) any other object just calls ToString. With this code
List<string> list = new List<string>();
list.Add("polo");
Dictionary<int, List<string>> dict = new Dictionary<int, List<string>>() ;
dict.Add(1, list);
Console.WriteLine(dump(list));
Console.WriteLine(dump(dict.First()));
Console.WriteLine(dump(dict));
you get the expected output:
{marco, }
1 => {marco, }
{1 => {marco, }, }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With