Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:
Heute, am 1. Dezember 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:
Reflection – Fluch oder Segen?
So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:
Reflection ist ein zentrales Element in .NET: Die Tatsache, dass in MSIL eben nicht nur der eigentliche Code, sondern auch die dazugehörigen Metadaten enthalten sind, zeigt dies. Und – das ist auch gut so.
Denn Reflection bildet die Grundlage für zahlreiche Szenarien, die von vielen Entwicklern tagtäglich genutzt werden: Sei es der Einsatz eines Microkernels und damit verbunden das dynamische Laden und späte Instanziieren von Typen, sei es der Einsatz einer dynamischen Sprache oder der dynamischen Sprachfeatures von C# 4.0. All dies basiert letztlich auf Reflection.
Auch die Verwendung von Attributen zur Laufzeit wäre ohne Reflection nicht möglich, ebensowenig wie die Analyse von Assemblies, um an Hand der enthaltenen Typen und deren Methoden, Eigenschaften und Feldern gewisse Entscheidungen zu treffen.
Reflection ist also fraglos ein Segen – ohne sie wären all die genannten Punkte in .NET nicht oder zumindest nicht mit dermaßen geringem Aufwand möglich. Doch woher stammt dann der schlechte Ruf, der in Gesprächen über Reflection zugegebenermaßen häufig den Unterton bestimmt?
Prinzipiell sind gleich zwei Gründe für den schlechten Ruf von Reflection verantwortlich, wobei einer der beiden systemimmanent ist, der zweite jedoch zumindest teilweise nur die konkrete Implementierung in .NET betrifft:
- Reflection ermöglicht den wahlfreien Zugriff auf Code, auch auf geschützte Elemente wie beispielsweise Variablen, die als private gekennzeichnet wurden.
- Reflection ist langsam – so wohl die Analyse von Code wie auch dessen Ausführung betreffend.
Dass Reflection den wahlfreien Zugriff auf beliebigen Code ermöglicht, ist Fluch und Segen zugleich: Auf der einen Seite wird auf diese Art sämtliche objektorientierte Programmierung ausgehebelt, auf der anderen Seite kann es ungemein nützlich sein, von außen auch auf private Elemente zugreifen zu können – man denke nur an diverse Unittests.
Nebenbei bemerkt: Einen interessanten Ansatz, wie Unittests ohne den Einsatz von Reflection auskommen und dennoch Interna testen können, hat Ralf Westphal in seinem Blogeintrag Zustand als Abhängigkeit beschrieben.
Generell gilt für den Einsatz von Reflection zum Zugriff auf geschützte Elemente also die gängige Empfehlung: So viel wie nötig und so wenig wie möglich – mit Betonung auf zweiterem.
Doch all dies wohnt Reflection bereits vom Ansatz her inne: Dabei spielt es keine Rolle, ob es sich um Reflection auf Basis von .NET, Java oder einer beliebigen anderen Plattform handelt: Diese Nachteile und die damit einhergehenden Überlegungen gelten generell.
Mit dem Umstand, dass Reflection unter .NET ausgesprochen langsam ist, sieht es jedoch ein wenig anders aus: Reflection an sich ist immer langsamer als statisch kompilierter Code – das ist logisch, wenn man weiß, wie beispielsweise Aufrufe von Funktionen in beiden Fällen funktionieren: Der Lookup per Reflection kostet schlicht und ergreifend mehr Zeit.
Doch Reflection kann auch dazu genutzt werden, um Code zu analyiseren – und hier ist es tatsächlich die Implementierung von .NET, welche die Schuld dafür trägt. Zudem zeichnet sich die Reflection von .NET in diesem Zusammenhang auch dadurch aus, wenig flexibel zu sein.
So ist es beispielsweise nicht möglich, aus einer auf dem Desktop ausgeführten .NET-Anwendung die Assembly mscorlib.dll von Silverlight zu öffnen – es kann nämlich jeweils nur eine Instanz der mscorlib.dll in einen Prozessraum geladen werden, was die Analyse einer anderen als der eigenen mscorlib.dll unter .NET immer scheitern wird.
Abhilfe – so wohl für die Geschwindigkeit wie auch für die Flexibilität – schafft der Einsatz einer anderen Komponente für Reflection, wie beispielsweise Mono Cecil, die unter anderem auch von Werkzeugen wie dem Mono Migration Analyzer oder dem Mono Debugger eingesetzt wird.
Zusammenfassend kann man also sagen, dass Reflection per se so wohl Fluch wie auch Segen ist – und dass es vom konkreten Kontext abhängt: Für Latebinding, dynamische Sprachen und Analyse von Code ist Reflection unverzichtbar, doch für den darüber hinausgehenden Einsatz von Reflection sollte man sehr gute Gründe haben.