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