Java Museum: Java Magazin kostenlos im entwickler.kiosk lesen!
Tipps und Tricks rund um .NET und Visual Studio

Eine einfache Objektausgabefunktion für alle .NET-Objekte

Kommentare

Dr. Holger Schwichtenberg (MVP) und FH-Prof. Manfred Steyer teilen in der Kolumne “.NETversum” ihr Expertenwissen rund um .NET-Tools und WPF mit.

Windows Developer

Dieser Teil der Kolumne “.NETversum” von Dr. Holger Schwichtenberg (MVP) und FH-Prof. Manfred Steyer ist erstmalig erschienen im Windows Developer 6.2012

Immer wieder steht man (nicht nur zu Testzwecken) vor der Aufgabe, ein Objekt mehr oder weniger komplett auf dem Bildschirm oder in eine Datei auszugeben. Die von System.Object an alle .NET-Objekte vererbte Methode ToString() erfüllt diesen Wunsch ganz und gar nicht, denn sie gibt im Standard leider immer nur den Klassennamen aus, solange man nicht ToString()explizit überschreibt. Die Werte aus dem Objekt für ToString() immer wieder neu durch das Zusammensammeln der einzelnen Fields und Properties zu implementieren, macht keinen Spaß und ist fehleranfällig. Wer denkt im Moment des Hinzufügens eines weiteren Fields oder Properties daran, dann auch ToString() anzupassen?

Erweiterungsmethoden für “System.Object”

Gesucht ist also ein generischer Ansatz. Hier kann man sehr schön mit Erweiterungsmethoden arbeiten. Die Klasse, die hier erweitert wird, ist einfach System.Object. Folglich wird diese Erweiterung an alle .NET-Klassen weitergegeben. Voilà! ToNameValueDictionary() liefert Attribute (sowohl Fields als auch Properties) mit den zugehörigen Werten als ein SortedDictionary. Dabei kann man auch mit dem Parameter onlyWithValue = false solche Werte ausschließen, die leer sind. Als “leer” werden in dieser Implementierung neben null-Werten auch leere Zeichenketten betrachtet. Auch Typen, die bei ToString() nicht mehr als ihren eigenen Klassennamen liefern, werden bei dieser Option herausgefiltert. Das Ziel ist, dass nur sinnvolle Informationen auf dem Bildschirm oder im Protokoll landen. Wenn der Wert von Field oder Property eine Liste ist, werden die Elemente der Liste durch ein Semikolon getrennt. In der nachstehenden Implementierung erfolgt das aber nicht rekursiv. Eine vollständige Serialisierung des .NET-Objekts ist diese Implementierung also nicht. Es geht hier aber auch gar nicht darum, SOAP oder JSON neu zu erfinden!

Meist wird man aber nicht ein SortedDictionary, sondern direkt eine ausgebbare Zeichenkette wünschen. Das leistet die Erweiterungsmethode ToNameValueString(), bei der man sowohl das Trennzeichen zwischen Name und Wert (nameValueSeparator) als auch das Trennzeichen zwischen zwei Attributen (attributeSeparator) wählen kann. Für nameValueSeparator ist der Standard ein Doppelpunkt mit gefolgtem Leerzeichen, für attributeSeparator ist es der Zeilenumbruch. Über showNames kann man steuern, ob die Namen der Attribute und der nameValueSeparator überhaupt ausgegeben werden. Bei showNames = false werden einfach nur die Werte aus den Attributen aneinandergereiht. Listing 5 zeigt den Code der beiden Erweiterungsmethoden ToNameValueDictionary() und ToNameValueString() sowie AddToDictionary(), eine Hilfsmethode für ToNameValueDictionary();.

Listing 5

  /// 
  /// Liefert den Inhalt eines Objekts als Zeichenketten der Form Name:Wert
  /// 
  public static string ToNameValueString(this Object obj, bool onlyWithValue = false, string attributeSeparator = "n", string nameValueSeparator = ": ", bool showNames = true)
  {
   if (attributeSeparator == "n") attributeSeparator = System.Environment.NewLine;
   SortedDictionary inhalt = obj.ToNameValueDictionary(onlyWithValue);
   StringBuilder sb = new StringBuilder();
   foreach (var de in inhalt)
   {
    if (sb.Length >0)
    {
     sb.Append(attributeSeparator);
    }
    sb.Append((showNames ? de.Key + nameValueSeparator : "") + de.Value);
   }
   return sb.ToString();
  }
 
  /// 
  /// Liefert den Inhalt eines Objekts als Dictionary-Objekt mit Namen und Werten
  /// 
  public static SortedDictionary ToNameValueDictionary(this Object obj, bool OnlyWithValue = false)
  {
   SortedDictionary Inhalt = new SortedDictionary();
   if (obj == null) return Inhalt;
   System.Type t = obj.GetType();
 
   // --- Schleife über alle Fields
   foreach (FieldInfo f in t.GetFields())
   {
    object Wert = f.GetValue(obj);
    if (OnlyWithValue == false || (Wert != null && Wert.ToString() != "") && Wert.ToString() != f.FieldType.ToString())
    {
     if (!Inhalt.ContainsKey(f.Name)) AddToDictionary(Inhalt, f.Name, Wert);
    }
   }
 
   // --- Schleife über alle Properties
   foreach (PropertyInfo p in t.GetProperties())
   {
    try
    {
     object Wert = p.GetValue(obj, null);
     if (OnlyWithValue == false || (Wert != null && Wert.ToString() != "") && Wert.ToString() != p.PropertyType.ToString())
     {
      AddToDictionary(Inhalt, p.Name, Wert);
     }
    }
    catch (Exception ex)
    {
     Inhalt.Add(p.Name, ex);
    }
 
   }
   return Inhalt;
  }
 
  /// 
  /// Hilfsroutine, die Wert an Dictionary anfügt und dabei über Liste iteriert, wenn Wert eine Liste ist
  /// 
  private static SortedDictionary AddToDictionary(SortedDictionary dic, string Name, Object Wert, bool onlyWithValue, Type type, string WertTrenner = ";")
  {
   if (Wert is IEnumerable && !(Wert is string))
   {
    StringBuilder sb = new StringBuilder();
    foreach (var item in (Wert as IEnumerable))
    {
     if (onlyWithValue == false || (item != null && item.ToString() != "") && item.ToString() != type.ToString())
     {
      sb.Append( (sb.Length>0 ? WertTrenner : "") + item.ToString());
     }
    }
    dic.Add(Name, sb.ToString());
   }
   else
   {
    if (onlyWithValue == false || (Wert != null && Wert.ToString() != "") && Wert.ToString() != type.ToString())
    {
     dic.Add(Name, Wert);
    }
   }
   return dic;
  }  
Einsatzbeispiel

Listing 6 zeigt ein kleines Einsatzbeispiel. Gegeben sei eine Klasse Softwarearchitekt, die sowohl Fields als auch Properties besitzt. ToString() ist überschrieben und ruft ToNameValueString() auf.

Listing 6

/// 
 /// Testklasse mit wilder Mischung aus Field und Properties
 /// 
 class Softwarearchitekt
 {
  public string Name { get; set; }
  public string Strasse;
  public string Wohnort;
  public int Alter { get; set; }
  public Uri Website;
  public List Themen = new List();
  public Firma Firma;
  public override string ToString()
  {
   return this.ToNameValueString();
  }
 }
 
 
 /// 
 /// Abhängige Klasse
 /// 
 public class Firma
 {
  public string Name;
 
  public override string ToString()
  {
   return  this.ToNameValueString(showNames:false);
  }
 }  

Wenn man nun eine Instanz dieser Klasse in Händen hält, z. B.:

var HS = new Softwarearchitekt();
HS.Name = "Holger Schwichtenberg";
HS.Wohnort = "Essen";
HS.Alter = 39;
HS.Firma = new Firma() { Name = "www.IT-Visions.de" };
HS.Website = new Uri("http://www.dotnet-doktor.de");
HS.Themen = new List() { ".NET", "Entity Framework", "WCF", "ASP.NET", "PowerShell"};  

Dann kann man nun sehr elegant zur Ausgabe des Objekts aufrufen:

HS.ToString();  

Damit erhält man folgende Zeichenkette:

Alter: 39
Firma: www.IT-Visions.de
Name: Holger Schwichtenberg
Strasse:
Themen: .NET;Entity Framework;WCF;ASP.NET;PowerShell
Website: http://www.dotnet-doktor.de/
Wohnort: Essen  

Den leeren Eintrag bei Strasse wird man los, wenn man stattdessen: HS.ToNameValueString(true) verwendet. Eine durch Semikola getrennte Liste nur der Werte erhält man mit:

HS.ToNameValueString(showNames: false, attributeSeparator: ";");  

Dr. Holger Schwichtenberg (MVP) und FH-Prof. Manfred Steyer arbeiten bei www.IT-Visions.de als Softwarearchitekten, Berater und Trainer für .NET-Technologien. Dabei unterstützen sie zahlreiche Unternehmen beim Einsatz von .NET und entwickeln selbst in größeren Projekten. Sie haben zahlreiche Fachbücher geschrieben und gehören seit vielen Jahren zu den Hauptsprechern auf der BASTA!. Manfred Steyer ist zudem für den Fachbereich “Software Engineering” der Studienrichtung “IT und Wirtschaftsinformatik” an der FH CAMPUS 02 in Graz verantwortlich. Dr. Holger Schwichtenberg unterrichtet in Lehraufträgen an den Fachhochschulen Münster und Graz.

Autor

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag