2013-04-07 10 views
7

Chcę funkcji, które można wywołać jako alternatywę dla .ToString(), który pokaże zawartość kolekcji.Jak przekonwertować IEnumerable <T> na ciąg, rekursywnie?

Próbowałem to:

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(); 
} 

ale drugi przeciążenie nigdy nie jest wywoływana. Na przykład:

List<string> list = new List<string>(); 
list.Add("polo"); 
Dictionary<int, List<string>> dict; 
dict.Add(1, list); 
Console.WriteLine(dump(dict)); 

Czekam tego wyjścia:

{1=>{"polo", }, } 

Co właściwie się dzieje, to: dict jest poprawnie interpretowana jako IEnumerable<KeyValuePair<int, List<string>>>, więc 3rd przeciążenie jest tzw.

3rd połączenia przeciążeniowe zrzucić na KeyValuePair>. Powinno to (?) Wywołać drugie przeciążenie, ale nie powoduje - zamiast tego wywołuje pierwsze przeciążenie.

więc uzyskać ten wynik: metodę

{[1=>System.Collections.Generic.List`1[System.String]], } 

który jest zbudowany z KeyValuePair za .ToString().

Dlaczego drugie przeciążenie nie jest wywoływane? Wydaje mi się, że środowisko wykonawcze powinno mieć wszystkie informacje potrzebne do zidentyfikowania KeyValuePair z pełnymi argumentami ogólnymi i wywołać to.

+0

nie jest to duplikat, ale możliwego zainteresowania: http://stackoverflow.com/questions/6032908/is-there-a-library -to-zapewnia-formatowaną-funkcję-zrzutu-linqpad – TrueWill

+0

Prawdopodobnie ma to związek z 'KeyValuePair' będącym strukturą zamiast klasą. –

+0

Czy to nie problem, że trzecie przeciążenie nie jest wywoływane dla 'Listy '? Otrzymujesz wyjście z drugiego przeciążenia, ale kiedy zrzuca parę, używa pierwszego zamiast trzeciego przeciążenia. Czy możesz spróbować wypisać właśnie 'dump (list)'? Ponadto: Czy debugujesz, aby dokładnie sprawdzić, jakie decyzje są podejmowane? Przejdź przez kod i bądź mądrzejszy! =) –

Odpowiedz

2

(aktualizacja) Jak wspomniano w innych odpowiedzi, problem jest, że kompilator nie wie, że typ V jest rzeczywiście List<string>, więc po prostu idzie do dump(object).

Możliwe obejście może być sprawdzenie typów w czasie wykonywania. Type.IsGenericType powie Ci, czy typ zmiennej ma rodzajowe, czy nie, a Type.GetGenericArguments da ci aktualny typ tych generycznych.

Więc można napisać jedną dump sposób odbierający obiekt i ignoruje wszelkie informacje rodzajowych. Zauważ, że używam interfejsu System.Collections.IEnumerable zamiast 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(); 
} 

To jest: a) zbiór jest potwierdzili, b) KeyValuePair pokazuje key => value, c) każdy inny obiekt po prostu wywołuje ToString. Z tym kodem

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)); 

uzyskać oczekiwany wynik:

{marco, } 
1 => {marco, } 
{1 => {marco, }, } 
+0

To zdecydowanie wydaje się podejście prawidłowe/jedyne - sprawdzanie typu runtime. Dzięki za przykład, dobrze jest znać dokładną składnię do radzenia sobie z lekami generycznymi w środowisku wykonawczym! –

0

Aby wywołać drugą wersję w foreach, trzeba określić parametry szablonu K i V, inaczej będzie zawsze zadzwonić pierwszą wersję:

dump(t); // always calls first version 
dump<K,V>(t); // will call the second 

Jak uzyskać typy parametrów K i V jest kolejne pytanie ...

+0

Spowoduje to błąd kompilatora, ponieważ T nie jest (zawsze) można zamieniać na listę (lub cokolwiek innego). – usr

+0

@usr - dobra uwaga, trochę wyjaśniłem swoją odpowiedź. –

5

Generics jest pojęciem czasu kompilacji, a nie czasem wykonywania. Innymi słowy, parametry typu są rozwiązywane w czasie kompilacji.

W twoim foreach dzwonisz pod numer dump(t), a t jest typu T. Ale nic nie wiadomo o T w tym miejscu poza tym, że jest to Object. Dlatego właśnie wywoływane jest pierwsze przeciążenie.

+0

Dobrze, to ma sens. Lub bardziej szczegółowo: generics są pojęciem wykonawczym, ale przeciążenia są koncepcją kompilacji. Dobrze? –

+0

nie! generics są rzeczą całkowicie kompilującą czas –

Powiązane problemy