Teure Typkonvertierung
Wichtiger Nachtrag siehe unten.
Ich habe den heutigen Vormittag dazu genutzt mich mit den Labs des neuen Visual Studio 2010 Training Kits zu beschäftigen. In einem der Labs ist mir ein ValueConverter für eine WPF-Anwendung aufgefallen. Dabei soll ein übergebener String-Value in einen Boolean konvertiert werden und umgekehrt.
Hier der Code für die Methode IValueConverter.Convert:
Da die Schnittstelle IValueConverter die Signatur und auch die Parameter für die Methode IValueConverter.Convert vorgibt, ist der Typ für den Parameter value vorgegeben. Für das oben gezeigte Beispiel ist die Verwendung eines try-catch Blocks bei der expliziten Konvertierung eines String zu einem Boolean recht teuer. Nebenbei bemerkt steht auch in den MSDN der wichtige Hinweis zur Verwendung eines catch-Blocks in C#:
Obwohl die catch-Klausel ohne Argumente verwendet wird, sodass sie jeden Ausnahmetyp abfängt, wird dies nicht empfohlen. Im Allgemeinen sollten Sie nur jene Ausnahmen abfangen, die Sie wiederherstellen können. Deshalb sollten Sie immer ein von System.Exception abgeleitetes Objektargument angeben.
Kommen wir aber zurück zum Stichwort “teuer”. Immer dann wenn eine explizite Konvertierung zu einem Fehler führt, wird von der Runtime eine Exception ausgelöst.
Das kostet Zeit!
Ich habe mir darum ein kleines Sample ausgedacht in dem ich erstens eine Verbesserung der vorliegenden Konvertierung implementiere und gleichzeitig im Vergleich zu der der Implementierung aus dem C# Lab, einen Zeitmessung einbaue.
Hier der Code den ich als Verbesserungsvorschlag für die Implementierung aus dem C# Lab erstellt habe
Hier eine kurze Erläuterung zum Aufbau meiner Konvertierungsversion. Als erstes schaue ich ob der Parameter null ist. Wenn dem so ist verlasse ich mit return false sofort wieder die Methode. Dadurch erspare ich mir den Weg über einen else-Zweig. Danach wird eine implizite Konvertierung von Objekt nach String vorgenommen. Das hat den Vorteil, sollte value nicht vom Typ System.String sein, dass keine Exception ausgelöst wird. Sollte value nicht vom Typ System.String sein, wäre nun temp null. Darum überprüfe ich mit einer if Anweisung temp auf null. Sollte temp wirklich null sein, verlasse ich die Methode sofort wieder mit return false. Nun wird der Inhalt von temp mit der Methode ToLower() in Kleinbuchstaben umgewandelt. Das ist notwendig da ich sonnst auf “true”, “True”, “false”, “False” etc. abfragen müsste. Besonders wichtig ist zu beachten, dass ich die Methode ToLower() vor der entsprechenden if-Anweisung aufrufe. Folgende Variante würde sicher keinen Fehler hervorrufen, jedoch einen unnötigen Aufruf der Methode ToLower() bedeuten.
So nicht:
if(temp.ToLower() == "true" || temp.ToLower() == "false")
Sollte die if-Anweisung true zurückgeben, wird temp mit der statischen Methode ToBoolean() aus der Klasse Convert in einen Boolean konvertiert und der Rückgabewert wird mit return zurückgegeben. Andernfalls wird die Methode mit return false wieder verlassen.
Ähnlich wie bei der Verwendung von Linq, halte ich mich bei der Überprüfung von Methodenparametern an die Regel “markieren, filtern und selektieren”. Soll heißen das ich mit jeder Überprüfung, Konvertierung oder Selektion von Parametern in einer Methode immer den kürzesten Weg aus der Methode heraus wähle.
Gibt es nun wirklich einen Zeitunterschied zwischen beiden Varianten? Ja! in meinem Beispiel habe ich die Konvertierung aus dem C# Sample von Microsoft wie folgt implementiert:
Damit die Zeitmessung nicht nur ein Zeitergebnis zurückliefert, verwende ich eine foreach-Schleife mit 20 Durchläufen. Damit der catch Block aufgerufen wird, übergeben ich einen Integer als Wert. Der Code für die Zeitmessung meiner Konvertierung schaut dann wie folgt aus:
Für die Zeitmessung der im Lab verwendeten Konvertierung wir der gleiche Code verwendet nur rufe ich dann die Methode CheckIsObjectBooleanII() auf. Das Ergebnis ist eindeutig.
Meine Konvertierung:
Die Konvertierung aus dem C# Lab:
Wie man an diesem einfachen Beispiel sehen kann, lohnt sich ein Refactoring von bestehendem Code mit den entsprechenden .NET-Mechanismen auf jeden Fall. Auch wenn es Code aus dem Hause Microsoft ist ;-) Wenn Ihr noch Vorschläge zur Verbesserung meiner Konvertierung habt, würde ich mich über ein Feedback per Mail sehr freuen.
Wichtiger Nachtrag:
Neben dem im Kommentar stehenden, sehr gutem, Hinweis von Albert Weinert, hat mich Albert auch noch auf die statische Methode TryParse() der Struktur Boolean hingewiesen. Diese Methode hat den großen Vorteil auch mit Whitespaces klar zu kommen. Dadurch wird die Methode sehr viel Fehlertoleranter.
Darum hier noch die dritte Variante der Methode CheckIsObjectBoolean().
Bei der Zeitmessung ergibt sich die gleiche Geschwindigkeit wie in meinem ersten Vorschlag aus der Methode CheckIsObjectBooleanI().
Vielen Dank Albert für diesen super Hinweis! Man lernt eben nie aus ;-)
TOM_MUE


2 Kommentare:
Albert Weinert schrieb:
Dies hier
if (temp == "true" || temp == "false")
return Convert.ToBoolean(temp);
else
return false;
Kann durch
if (temp == "true")
return true;
else
return false;
ersetzt werden.
Freundliche Grüße
Albert
Ganz ehrlich geht die Zeiteinsparung hier auf Kosten der Sicherheit. Ein übergebener String "true" oder "false" wird hier genauso erfolgreich funktionieren, wie true / false als Boolean. Zudem muss das Ergebnis von "value as string" unbedingt auf "true" "false" lauten. Sollte das durch Lokalisierungen (sofern das möglich ist) auf "Wahr" "Falsch" umgestellt sein, wird die Routine nicht mehr zuverlässig arbeiten.
Das Beispiel zeigt aber gut, dass Try Catch einfach ein Zeitfaktor sind, entgegen vieler gegensätzlicher Behauptungen.
Mario Noack
Kommentar veröffentlichen
Links zu diesem Post:
Link erstellen
<< Startseite