it-swarm.dev

Gibt es Probleme bei der Verwendung von Reflection?

Ich weiß nicht warum, aber ich habe immer das Gefühl, dass ich "betrüge", wenn ich Reflexion verwende - vielleicht liegt es an dem Performance-Hit, von dem ich weiß, dass ich ihn nehme.

Ein Teil von mir sagt, wenn es Teil der Sprache ist, die Sie verwenden, und es das erreichen kann, was Sie versuchen, warum dann nicht? Der andere Teil von mir sagt, es muss einen Weg geben, wie ich dies ohne Reflexion tun kann. Ich denke, vielleicht hängt es von der Situation ab.

Was sind die potenziellen Probleme, auf die ich bei der Verwendung von Reflexion achten muss, und wie besorgt sollte ich darüber sein? Wie viel Aufwand lohnt es sich, nach einer konventionelleren Lösung zu suchen?

51
P B

Nein, es ist kein Betrug - es ist eine Möglichkeit, Probleme in einigen Programmiersprachen zu lösen.

Jetzt ist es oft nicht die beste (sauberste, einfachste, am einfachsten zu wartende) Lösung. Wenn es einen besseren Weg gibt, verwenden Sie diesen in der Tat. Manchmal gibt es jedoch keine. Oder wenn ja, ist es einfach viel komplexer und beinhaltet viel Code-Duplizierung usw., was es unmöglich macht (auf lange Sicht schwierig zu warten).

Zwei Beispiele aus unserem aktuellen Projekt (Java):

  • einige unserer Testtools verwenden Reflection, um die Konfiguration aus XML-Dateien zu laden. Die zu initialisierende Klasse verfügt über bestimmte Felder, und der Konfigurationslader verwendet Reflection, um das XML-Element mit dem Namen fieldX mit dem entsprechenden Feld in der Klasse abzugleichen und dieses zu initialisieren. In einigen Fällen kann aus den identifizierten Eigenschaften im laufenden Betrieb ein einfaches GUI-Dialogfeld erstellt werden. Ohne Reflexion würde dies Hunderte von Codezeilen über mehrere Anwendungen hinweg erfordern. Durch Reflexion konnten wir schnell und ohne großen Aufwand ein einfaches Tool zusammenstellen und uns auf den wichtigen Teil (Regressionstests unserer Web-App, Analyse von Serverprotokollen usw.) und nicht auf den irrelevanten konzentrieren.
  • ein Modul unserer Legacy-Webanwendung sollte Daten aus DB-Tabellen in Excel-Tabellen und zurück exportieren/importieren. Es enthielt viel duplizierten Code, wobei die Duplikate natürlich nicht genau gleich waren, einige enthielten Fehler usw. Durch Reflexion, Selbstbeobachtung und Anmerkungen gelang es mir, den größten Teil der Duplizierung zu beseitigen und die Codemenge von oben zu reduzieren 5K bis unter 2,4K, während der Code robuster und einfacher zu warten oder zu erweitern ist. Jetzt war dieses Modul für uns kein Problem mehr - dank des vernünftigen Einsatzes von Reflexion.

Unter dem Strich kann wie bei jedem leistungsstarken Werkzeug auch die Reflexion verwendet werden, um sich in den Fuß zu schießen. Wenn Sie lernen, wann und wie (nicht) Sie es verwenden sollen, können Sie elegante und saubere Lösungen für ansonsten schwierige Probleme finden. Wenn Sie es missbrauchen, können Sie ein ansonsten einfaches Problem in ein komplexes und hässliches Chaos verwandeln.

48
Péter Török

Es betrügt nicht. Aber es ist normalerweise aus mindestens den folgenden Gründen eine schlechte Idee im Produktionscode:

  • Sie verlieren die Sicherheit vom Typ zur Kompilierungszeit - Es ist hilfreich, wenn der Compiler überprüft, ob eine Methode zur Kompilierungszeit verfügbar ist. Wenn Sie Reflection verwenden, wird zur Laufzeit ein Fehler angezeigt, der sich auf Endbenutzer auswirken kann, wenn Sie nicht gut genug testen. Selbst wenn Sie den Fehler abfangen, wird das Debuggen schwieriger.
  • Es verursacht Fehler beim Refactoring - Wenn Sie auf ein Mitglied basierend auf seinem Namen zugreifen (z. B. mithilfe einer fest codierten Zeichenfolge), wird dies von den meisten Code-Refactoring-Tools nicht geändert und Sie haben es sofort Ein Fehler, der möglicherweise nur schwer aufzuspüren ist.
  • Leistung ist langsamer - Die Reflexion zur Laufzeit ist langsamer als bei statisch kompilierten Methodenaufrufen/Variablensuchen. Wenn Sie nur gelegentlich reflektieren, spielt dies keine Rolle. Dies kann jedoch zu einem Leistungsengpass werden, wenn Sie tausende oder millionenfach pro Sekunde über Reflexion telefonieren. Ich habe einmal eine 10-fache Beschleunigung in einem Clojure-Code erhalten, indem ich einfach alle Reflexionen eliminiert habe. Ja, das ist ein echtes Problem.

Ich würde vorschlagen, die Verwendung von Reflexion auf die folgenden Fälle zu beschränken:

  • Für Quick Prototyping oder "Wegwerf" -Code, wenn es die einfachste Lösung ist
  • Für echte Reflexionsanwendungsfälle, z. IDE Tooling, mit dem ein Benutzer zur Laufzeit die Felder/Methoden eines beliebigen Objekts untersuchen kann.

In allen anderen Fällen würde ich vorschlagen, einen Ansatz zu finden, der Reflexion vermeidet. Definieren einer Schnittstelle mit den entsprechenden Methoden und deren Implementierung in der Gruppe von Klassen, für die Sie die Methode (n) aufrufen möchten, reicht normalerweise aus, um die meisten einfachen Fälle zu lösen.

38
mikera

Reflexion ist nur eine andere Form der Metaprogrammierung und genauso gültig wie die typbasierten Parameter, die Sie heutzutage in den meisten Sprachen sehen. Reflexion ist leistungsfähig und allgemein, und Reflexionsprogramme haben eine hohe Wartbarkeit (natürlich bei korrekter Verwendung) und dies mehr als rein objektorientierte oder prozedurale Programme. Ja, Sie zahlen einen Leistungspreis - aber ich würde mich gerne für ein langsameres Programm entscheiden, das in vielen oder sogar den meisten Fällen besser gewartet werden kann.

11
DeadMG

Sicher hängt alles davon ab, was Sie erreichen wollen.

Zum Beispiel habe ich eine Medienprüfanwendung geschrieben, die mithilfe der Abhängigkeitsinjektion bestimmt, welche Art von Medien (MP3-Dateien oder JPEG-Dateien) überprüft werden sollen. Die Shell musste ein Raster mit den relevanten Informationen für jeden Typ anzeigen, hatte jedoch keine Kenntnis von was, das angezeigt werden sollte. Dies wird in der Assembly definiert, die diesen Medientyp liest.

Daher musste ich Reflection verwenden, um die Anzahl der anzuzeigenden Spalten sowie deren Typen und Namen zu ermitteln, damit ich das Raster korrekt einrichten konnte. Es bedeutete auch, dass ich die injizierte Bibliothek aktualisieren (oder eine neue erstellen) konnte, ohne einen anderen Code oder eine andere Konfigurationsdatei zu ändern.

Die einzige andere Möglichkeit wäre gewesen, eine Konfigurationsdatei zu haben, die aktualisiert werden müsste, wenn ich den zu überprüfenden Medientyp wechseln würde. Dies hätte einen weiteren Fehlerpunkt für die Anwendung eingeführt.

8
ChrisF

Reflexion ist fantastisch, um Tools für Entwickler zu erstellen.

Da es Ihrer Build-Umgebung ermöglicht, den Code zu überprüfen und möglicherweise die richtigen Tools zum Bearbeiten/Init-Überprüfen des Codes zu generieren.

Als allgemeine Programmiertechnik kann sie nützlich sein, ist aber spröder als die meisten Menschen glauben.

Eine echte Entwicklungsanwendung für Reflection (IMO) ist, dass das Schreiben einer generischen Streaming-Bibliothek sehr einfach ist (solange sich Ihre Klassenbeschreibung nie ändert (dann wird es zu einer sehr spröden Lösung)).

7
Martin York

Reflexion ist ein großartiges Werkzeug, wenn Sie ein Bibliothek Autor sind und daher keinen Einfluss auf die eingehenden Daten haben. Eine Kombination aus Reflexion und Metaprogrammierung kann es Ihrer Bibliothek ermöglichen, nahtlos mit beliebigen Anrufern zusammenzuarbeiten, ohne dass diese durch die Bereiche der Codegenerierung usw. springen müssen.

Ich versuche jedoch, von der Reflexion in application code abzuraten. Auf der App-Ebene sollten Sie verschiedene Metaphern verwenden - Schnittstellen, Abstraktion, Kapselung usw.

7
Marc Gravell

Die Verwendung von Reflexion ist in OO Sprachen) oft schädlich, wenn sie nicht mit großem Bewusstsein verwendet wird.

Ich habe die Anzahl der schlechten Fragen verloren, die ich auf StackExchange-Websites gesehen habe

  1. Die verwendete Sprache ist OO
  2. Der Autor möchte herausfinden, welcher Objekttyp übergeben wurde, und dann eine seiner Methoden aufrufen
  3. Der Autor weigert sich zu akzeptieren, dass Reflexion die falsche Technik ist und beschuldigt jeden, der darauf hinweist, "nicht zu wissen, wie er mir helfen kann".

hiersind typische Beispiele.

Der größte Punkt von OO ist das)

  1. Sie gruppieren Funktionen, die wissen, wie man eine Struktur/ein Konzept mit dem Ding selbst manipuliert.
  2. An jedem Punkt Ihres Codes sollten Sie genug über den Typ eines Objekts wissen, um zu wissen, welche relevanten Funktionen es hat, und ihn bitten, seine bestimmte Version dieser Funktionen aufzurufen.
  3. Sie stellen die Wahrheit des vorherigen Punkts sicher, indem Sie für jede Funktion den richtigen Eingabetyp angeben und jede Funktion nicht mehr wissen (und nicht mehr tun) als relevant ist.

Wenn zu irgendeinem Zeitpunkt in Ihrem Code Punkt 2 für ein Objekt, das Sie übergeben haben, nicht gültig ist, sind eines oder mehrere davon wahr

  1. Der falsche Eingang wird eingespeist
  2. Ihr Code ist schlecht strukturiert
  3. Sie haben keine Ahnung, was zum Teufel Sie tun.

Schlecht ausgebildete Entwickler verstehen das einfach nicht und glauben, dass ihnen in jedem Teil ihres Codes alles übergeben werden kann, und tun, was sie wollen, aus einer (fest codierten) Reihe von Möglichkeiten. Diese Idioten benutzen viel Reflexion .

Für OO Sprachen) sollte Reflexion nur in Metaaktivitäten (Klassenlader, Abhängigkeitsinjektion usw.) erforderlich sein. In diesen Kontexten ist Reflexion erforderlich, da Sie einen generischen Dienst zur Unterstützung der Manipulation/Konfiguration bereitstellen von Code, von dem Sie aus einem guten und legitimen Grund nichts wissen. In fast jeder anderen Situation, wenn Sie nach Reflexion greifen, dann machen Sie etwas falsch und müssen sich fragen warum Dieser Code weiß nicht genug über das Objekt, das an ihn übergeben wurde.

5
itsbruce

Eine Alternative in Fällen, in denen die Domäne der reflektierten Klassen genau definiert ist, besteht darin, die Reflexion zusammen mit anderen Metadaten zu verwenden m Code zu generieren, anstatt die Reflexion zur Laufzeit zu verwenden. Ich mache das mit FreeMarker/FMPP; Es gibt viele andere Tools zur Auswahl. Dies hat den Vorteil, dass Sie am Ende "echten" Code erhalten, der leicht debuggt werden kann usw.

Abhängig von der Situation kann dies Ihnen helfen, viel schnelleren Code zu erstellen - oder nur viel Code aufzublähen. Es vermeidet die Nachteile der Reflexion:

  • verlust der Sicherheit zur Kompilierungszeit
  • fehler aufgrund von Refactoring
  • langsamere Leistung

zuvor erwähnt.

Wenn sich Reflexion wie Betrug anfühlt, kann dies daran liegen, dass Sie sich auf viele Vermutungen stützen, bei denen Sie sich nicht sicher sind, und Ihr Bauch warnt Sie, dass dies riskant ist. Stellen Sie sicher, dass Sie eine Möglichkeit bieten, die der Reflexion innewohnenden Metadaten mit Ihren eigenen Metadaten zu erweitern, in denen Sie alle Macken und Sonderfälle der realen Klassen beschreiben können, auf die Sie möglicherweise stoßen.

3
Ed Staub

Ein Problem, das wir mit Reflection hatten, ist, als wir Obfuscation zur Mischung hinzugefügt haben. Alle Klassen erhalten neue Namen und plötzlich funktioniert das Laden einer Klasse oder Funktion anhand ihres Namens nicht mehr.

2
Carra

Es ist kein Betrug, aber wie jedes Werkzeug sollte es für das verwendet werden, was es lösen soll. Reflexion ermöglicht es Ihnen per Definition, Code durch Code zu überprüfen und zu ändern. Wenn Sie dies tun müssen, ist Reflexion das Werkzeug für den Job. Bei der Reflexion dreht sich alles um Metacode: Code, der auf Code abzielt (im Gegensatz zu regulärem Code, der auf Daten abzielt).

Ein Beispiel für eine gute Verwendung von Reflexionen sind generische Webdienstschnittstellenklassen: Ein typisches Design besteht darin, die Protokollimplementierung von der Nutzlastfunktionalität zu trennen. Dann haben Sie eine Klasse (nennen wir es T), die Ihre Nutzdaten implementiert, und eine andere, die das Protokoll implementiert (P). T ist ziemlich einfach: Schreiben Sie für jeden Aufruf, den Sie tätigen möchten, einfach eine Methode, die alles tut, was sie tun soll. P muss jedoch Webdienstaufrufe Methodenaufrufen zuordnen. Es ist wünschenswert, dieses Mapping generisch zu machen, da es Redundanz vermeidet und P in hohem Maße wiederverwendbar macht. Reflection bietet die Möglichkeit, die Klasse T zur Laufzeit zu untersuchen und ihre Methoden basierend auf Zeichenfolgen aufzurufen, die über das Webdienstprotokoll an P übergeben werden, ohne dass die Klasse T zur Kompilierungszeit bekannt ist . Mit der Regel 'Code über Code' kann man argumentieren, dass die Klasse P den Code in der Klasse T als Teil ihrer Daten hat.

Jedoch.

Reflection bietet Ihnen auch Tools, mit denen Sie Einschränkungen des Typensystems der Sprache umgehen können. Theoretisch können Sie alle Parameter als Typ object übergeben und ihre Methoden durch Reflektionen aufrufen. Voilà, die Sprache, die eine starke statische Typisierungsdisziplin erzwingen soll, verhält sich jetzt wie eine dynamisch typisierte Sprache mit später Bindung, nur dass die Syntax weitaus ausgefeilter ist. Jede einzelne Instanz eines solchen Musters, die ich bisher gesehen habe, war ein schmutziger Hack, und ausnahmslos wäre eine Lösung innerhalb des Typensystems der Sprache möglich gewesen, und es wäre in jeder Hinsicht sicherer, eleganter und effizienter gewesen .

Es gibt einige Ausnahmen, z. B. GUI-Steuerelemente, die an verschiedene nicht verwandte Arten von Datenquellen gebunden werden können. Es ist nicht realistisch, dass Ihre Daten eine bestimmte Schnittstelle implementieren, damit Sie sie binden können, und der Programmierer implementiert auch keinen Adapter für jeden Datenquellentyp. In diesem Fall ist die Verwendung der Reflexion zum Erkennen des Datenquellentyps und zum Anpassen der Datenbindung eine nützlichere Wahl.

2
tdammers

Es kommt total darauf an. Ein Beispiel für etwas, das ohne Reflexion schwer zu tun wäre, wäre das Replizieren von ObjectListView . Es generiert auch IL-Code im laufenden Betrieb.

1
Tangurena

Reflexion ist die primäre Methode zur Erstellung konventioneller Systeme. Es würde mich nicht wundern, wenn es in den meisten MVC-Frameworks häufig verwendet wird. Es ist eine Hauptkomponente in ORMs. Möglicherweise verwenden Sie bereits Komponenten, die jeden Tag damit erstellt wurden.

Die Alternative für eine solche Verwendung ist die Konfiguration, die ihre eigenen Nachteile hat.

1
Chris McCall

Reflexion kann Dinge erreichen, die sonst praktisch nicht möglich sind.

Überlegen Sie beispielsweise, wie Sie diesen Code optimieren können:

int PoorHash(char Operator, int seed, IEnumerable<int> values) {
    foreach (var v in values) {
        seed += 1;
        switch (char) {
            case '+': seed += v; break;
            case '^': seed ^= v; break;
            case '-': seed -= v; break;
            ...
        }
        seed *= 3;
    }
    return seed;
}

Es gibt einen teuren Testschlag in der Mitte der inneren Schleife, aber um ihn zu extrahieren, muss die Schleife für jeden Operator einmal neu geschrieben werden. Durch Reflexion können wir die Leistung auf das Niveau des Extrahierens dieses Tests bringen, ohne die Schleife ein Dutzend Mal zu wiederholen (und dadurch die Wartbarkeit zu beeinträchtigen). Generieren und kompilieren Sie einfach die Schleife, die Sie im laufenden Betrieb benötigen.

Ich habe tatsächlich diese Optimierung durchgeführt , obwohl es eine etwas kompliziertere Situation war und die Ergebnisse erstaunlich waren. Eine um eine Größenordnung verbesserte Leistung und weniger Codezeilen.

(Hinweis: Ich habe anfangs versucht, einen Func anstelle eines Zeichens zu übergeben, und es war etwas besser, aber nicht annähernd die 10-fache Reflexion.)

0
Craig Gidney