Top 10 Fehler–#12: Naiver Einsatz von Reflection

4. September 2014

Dies ist Teil 13 der kleinen Serie, die ich als Kommentar zum Blog-Beitrag Top 10 Mistakes that C# Programmers Make begonnen habe.

Und mittlerweile bin ich bei den Themen angekommen, die mir in der Liste noch fehlen. Ich zähle also munter weiter ;-)…

 

AJ’s Common Mistake #12: Naiver Einsatz von Reflection

Ich habe vor Jahren eine frühe Version von Spring.NET als DI-Container eingesetzt (Unity gab es damals noch nicht). Objekte wurden dort per Activator.CreateObject erzeugt – die dokumentierte, offensichtliche und nicht zu Letzt die langsamste Variante der Objekterzeugung. (Disclaimer: Ich weiß nicht, wie das heute in Spring.NET gelöst ist.)

Ich habe bei einem Kunden an einem Framework mitgearbeitet, das für die Beschreibung von HOST-Daten Attribute verwendete und die jedes mal via Type.GetCustomAttributes ausgelesen hat. Auch nicht die schnellste Variante.

Ein Kollege verwendete in seinem O/R-Mapper die üblichen Methoden GetValue, etc. um Datenstrukturen generisch zu lesen und zu schreiben. Suboptimal.

Die Beispiele habe ich ausgewählt, weil sie Reflection in wiederverwendbaren Komponenten an zentraler Stelle einsetzten. Das bringt natürlich entsprechende – im Bezug auf Performance leider negative – Auswirkungen mit sich. Die Liste ließe sich problemlos fortsetzen, insbesondere wenn man noch anwendungsspezifische Einsatzszenarien hinzunimmt.

 

Reflection ist ein sehr mächtiges Werkzeug, das sehr generische – und damit besser wiederverwendbare – Lösungen ermöglicht. Damit verknüpft ist aber ein Preisschild, das leider oft übersehen wird: Performance.

Bei Performance-Analysen, die ich immer mal wieder mache, ist Reflection (wenn es denn zum Einsatz kommt) eigentlich immer ein Kandidat für Optimierungen.

Die wichtigste Empfehlung ist zunächst, dass man sich über die Kosten von Reflection bewusst sein sollte. Im Debugger oder im UnitTests sieht das Ergebnis immer gut aus. Erst ein Performancetest, der den Aufruf 10.000 mal durchführt, wird das Zeitverhalten offensichtlich werden lassen.

Natürlich sollte man an dieser Stelle immer hinterfragen, wie oft der besagte Code in der Anwendung aufgerufen wird. Sporadische Aufrufe haben sicher kaum Einfluss auf das Laufzeitverhalten der Anwendung insgesamt.

Hat man ein Performance-Bottleneck identifiziert, gibt es je nach konkretem Fall verschiedene Lösungsansätze:

  • Andere Sprachmittel können zum Einsatz kommen. Gerade älterer Code kann womöglich durch Generics ohne Reflection gelöst werden. Auch der Einsatz von dynamic kann je nach Implementierung Vorteile bringen und kommt etwa bei ASP.NET MVC zum Einsatz.
  • Code-Generierung ist ein valider Ansatz, der z.B. von ASP.NET und Entity Framework ausgiebig genutzt wird. Mit T4-Templates kein Problem, auch für andere Themen.
  • Patterns können Sonderfälle effizienter abbilden, z.B. Factory-Ansätze wie Prototyp für die Objekterzeugung.
  • Für Meta-Informationen (etwa custom attributes) bietet sich Caching der Informationen an.
  • Dynamische Code-Generierung zur Laufzeit – von .NET etwas intern für regular expressions genutzt – ist dank dynamic methods auch ohne CodeDOM einfach verwendbar.

Weiterführende Informationen:

Beachten muss man dabei allerdings auch, dass Microsoft über die verschiedenen .NET-Versionen hinweg natürlich auch immer an der Performanceschraube gedreht hat. Es gilt also grundsätzlich das Mantra jeder Optimierung: Messen! Messen! Messen!