How to convert IEnumerable <T> to string, recursively?
I need a function that I can call an alternative to .ToString (), which will show the contents of the collections.
I 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 is never 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 expect this conclusion:
{1=>{"polo", }, } What actually happens: dict is correctly interpreted as IEnumerable<KeyValuePair<int, List<string>>> , so the 3rd overload is called.
3rd overload causes a reset to KeyValuePair>. This should (?) Cause a second overload, but it doesnโt - it causes the first overload.
So we get this result:
{[1=>System.Collections.Generic.List`1[System.String]], } which is built from the KeyValuePair.ToString () method.
Why is the second overload not called? It seems to me that the runtime should have all the necessary information to identify KeyValuePair with full general arguments and call it.
(updated) As mentioned in other answers, the problem is that the compiler does not know that type V is actually List<string> , so it just goes to dump(object) .
A possible workaround might be runtime type checking. Type.IsGenericType will tell you if the type of the variable is generic or not, and Type.GetGenericArguments will give you the actual type of these generics.
So, you can write one dump method that receives the object and ignore any generic information. Note that I am using the System.Collections.IEnumerable interface, not System.Collections.Generics.IEnumerable<T> .
public static string dump(Object o) { Type type = o.GetType(); // if it a generic, check if it 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) iteration of the collection, b) a pair with a key shows key => value , c) any other object simply calls ToString . Using 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 will get the expected result:
{marco, } 1 => {marco, } {1 => {marco, }, } Generics is a compile-time concept, not a launch time. In other words, type parameters are allowed at compile time.
In your foreach, you call dump(t) and t is of type T. But at that time nothing is known about T, except that it is an Object . This is why the first overload is called.
To call the second version in foreach , you need to specify the template parameters K and V , otherwise it will always call the first version:
dump(t); // always calls first version dump<K,V>(t); // will call the second How you get the parameter types K and V is another question ....