2013-04-07 13 views
7

Ich möchte eine Funktion, die ich als Alternative zu .ToString() aufrufen kann, die den Inhalt von Sammlungen anzeigen wird.Wie konvertiere ich IEnumerable <T> in String, rekursiv?

Ich habe dies versucht:

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

aber die zweite Überlast wird nie genannt. Zum Beispiel:

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

Ich erwarte diese Ausgabe:

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

Was passiert eigentlich, ist dies: dict korrekt als IEnumerable<KeyValuePair<int, List<string>>> interpretiert wird, so wird die dritte Überlastung bezeichnet.

Die 3. Überladung ruft Dump auf einem KeyValuePair> auf. Dies sollte (?) Die zweite Überladung aufrufen, aber es tut dies nicht - es ruft stattdessen die erste Überladung auf.

So bekommen wir diese Ausgabe:

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

, die von KeyValuePair der .ToString() Methode erstellt wird.

Warum wird die zweite Überlast nicht aufgerufen? Es scheint mir, dass die Laufzeit alle Informationen haben sollte, die benötigt werden, um eine KeyValuePair mit vollständigen generischen Argumenten zu identifizieren und diese aufzurufen.

+0

kein Duplikat, aber von möglichem Interesse: http://stackoverflow.com/questions/6032908/is-there-a-library -that-a-formatiert-dump-function-like-linqpad – TrueWill

+0

Wahrscheinlich hat mit 'KeyValuePair' zu tun, eine Struktur anstelle einer Klasse. –

+0

Ist nicht das Problem eher, dass die dritte Überladung nicht für die "Liste " aufgerufen wird? Sie erhalten die Ausgabe von der zweiten Überladung, aber wenn es das Paar abgibt, verwendet es die erste anstelle der dritten Überladung. Können Sie versuchen, nur 'dump (list)' zu schreiben? Außerdem: Haben Sie Fehler gemacht, um genau zu überprüfen, welche Entscheidungen getroffen wurden? Geh durch den Code und werde weiser! =) –

Antwort

2

(aktualisiert) Wie in anderen Antworten erwähnt, ist das Problem, dass der Compiler nicht diese Art weiß V ist eigentlich ein List<string>, so geht es nur um dump(object).

Eine mögliche Problemumgehung könnte sein, Typen zur Laufzeit zu überprüfen. Type.IsGenericType wird Ihnen sagen, ob der Typ einer Variablen Generika hat oder nicht, und Type.GetGenericArguments wird Ihnen den tatsächlichen Typ dieser Generika geben.

So können Sie eine einzige dump Methode ein Objekt empfangen und Generika-Informationen zu ignorieren. Beachten Sie, dass ich die System.Collections.IEnumerable Schnittstelle anstelle System.Collections.Generics.IEnumerable<T> verwende.

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

, die: a) eine Sammlung iteriert wird, b) ein KeyValuePair zeigt key => value, c) jedes anderes Objekt aufruft nur ToString. Mit diesem 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)); 

Sie die erwartete Ausgabe:

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

Dies scheint definitiv die richtige/nur Ansatz - Laufzeittyp Prüfung. Danke für das Beispiel, es ist gut, die genaue Syntax für den Umgang mit Generika zur Laufzeit zu kennen! –

0

Um die zweite Version in Ihrem foreach aufrufen, müssen Sie die Template-Parameter K und V angeben, sonst wird es die erste Version jederzeit anrufen:

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

Wie Sie die Parametertypen K und V erhalten ist eine andere Frage ....

+0

Dies führt zu einem Compilerfehler, da T nicht (immer) in die Liste (oder was auch immer) konvertiert werden kann. – usr

+0

@ usr - guter Punkt, klärte ich meine Antwort ein wenig. –

5

Generics ist ein Kompilierzeitkonzept, keine Laufzeit. Mit anderen Worten, die Typ-Parameter werden zur Kompilierzeit aufgelöst.

In Ihrer foreach rufen Sie dump(t) und t ist vom Typ T. Aber es ist nichts über T zu diesem Zeitpunkt bekannt, außer dass es eine Object ist. Deshalb wird die erste Überladung aufgerufen.

+0

Richtig, das macht Sinn. Oder genauer gesagt: Generika sind ein Laufzeitkonzept, aber Überladungen sind ein Kompilierungszeitkonzept. Recht? –

+0

nein! Generika sind eine komplette Kompilierzeit Sache –

Verwandte Themen