Как рекурсивно преобразовать 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 - это другой вопрос....