Как рекурсивно преобразовать IEnumerable в string?
Я хочу функцию, которую я могу вызвать в качестве альтернативы .ToString (), который покажет содержимое коллекций.
Я попытался это:
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();
}
но вторая перегрузка никогда не вызывается. Например:
List<string> list = new List<string>();
list.Add("polo");
Dictionary<int, List<string>> dict;
dict.Add(1, list);
Console.WriteLine(dump(dict));
Я ожидаю этого вывода:
{1=>{"polo", }, }
на самом деле происходит следующее:
dict правильно интерпретируется как IEnumerable<KeyValuePair<int, List<string>>>
, поэтому вызывается 3-я перегрузка.
3-я перегрузка вызывает дамп на KeyValuePair>. Это должно(?) вызовите вторую перегрузку, но это не так-вместо этого он вызывает первую перегрузку.
Итак, мы получаем этот вывод:
{[1=>System.Collections.Generic.List`1[System.String]], }
который построен из KeyValuePair .метод toString.
почему не вызывается вторая перегрузка? Мне кажется, что среда выполнения должна иметь всю информацию, необходимую для идентификации KeyValuePair с полными универсальными аргументами и вызова этого.
3 ответов
(обновлено) как упоминалось в других ответах, проблема в том, что компилятор не знает этот тип V
на самом деле List<string>
, поэтому он просто идет к dump(object)
.
возможное решение этой проблемы может быть проверка типов во время выполнения. Type.IsGenericType
сообщит вам, имеет ли тип переменной дженерики или нет, и Type.GetGenericArguments
даст вам фактический тип этих дженериков.
таким образом, вы можете написать один dump
метод получения объекта и игнорирование любых generics info. Обратите внимание, что я использую System.Collections.IEnumerable
интерфейс, а не 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();
}
то есть: a) коллекция повторяется, b) keyvaluepair показывает key => value
, c) любой другой объект просто вызывает ToString
. С помощью этого кода
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));
вы получаете ожидаемый результат:
{marco, }
1 => {marco, }
{1 => {marco, }, }
Generics-это концепция времени компиляции, а не время выполнения. Другими словами, параметры типа разрешаются во время компиляции.
в вашем foreach вы вызываете dump(t)
и t имеет тип T.
Но на данный момент о T ничего не известно, кроме того, что это Object
.
Вот почему называется первая перегрузка.
чтобы вызвать вторую версию в вашем foreach
, вам необходимо указать параметры шаблона K
и V
, в противном случае он всегда будет вызывать первую версию:
dump(t); // always calls first version
dump<K,V>(t); // will call the second
как вы получаете типы параметров K
и V
- это другой вопрос....