7 Stimmen

Erkennung des vom Compiler erzeugten Standardkonstruktors mithilfe von Reflection in C#

Ich habe .NET 3.5 SP1 im Visier und verwende KommentarChecker um meine XML-Dokumentation zu validieren, funktioniert alles gut, bis ich zu einer Klasse wie dieser komme:

/// <summary>
/// documentation
/// </summary>
public sealed class MyClass {
    /// <summary>
    /// documentation
    /// </summary>
    public void Method() {
    }
}

In dem obigen Beispiel generiert der Compiler, soweit ich weiß, einen Standardkonstruktor für meine Klasse. Das Problem dabei ist, dass CommentChecker Warnungen erzeugt, die mir sagen, dass dem Konstruktor die Kommentare fehlen.

Ich habe versucht, das Programm so zu modifizieren, dass es diesen Sonderfall erkennt und ignoriert, aber ich komme nicht weiter, ich habe es bereits mit IsDefined(typeof(CompilerGeneratedAttribute), true) aber das hat nicht funktioniert.

Also kurz gesagt, wie kann ich den Standardkonstruktor mit Reflexion erkennen?

6voto

casperOne Punkte 72238

Wenn Sie bereit sind, ein wenig in der IL zu recherchieren, dann können Sie Die meisten des Weges dorthin.

Erstens, vorausgesetzt, Sie haben ConstructorInfo Instanz, von der Sie wissen, dass sie parameterlos ist, können Sie den Methodenrumpf und die Bytes für den Methodenrumpf wie folgt abrufen (wir werden mit dem Aufbau einer Erweiterungsmethode beginnen, um dies zu tun):

public static bool MightBeCSharpCompilerGenerated(
    this ConstructorInfo constructor)
{
    // Validate parmaeters.
    if (constructor == null) throw new ArgumentNullException("constructor");

    // If the method is static, throw an exception.
    if (constructor.IsStatic)
        throw new ArgumentException("The constructor parameter must be an " +
            "instance constructor.", "constructor");

    // Get the body.
    byte[] body = constructor.GetMethodBody().GetILAsByteArray();

Sie können alle Methodenkörper ablehnen, die keine sieben Bytes haben.

    // Feel free to put this in a constant.
    if (body.Length != 7) return false;

Der Grund dafür wird im folgenden Code deutlich werden.

In Abschnitt I.8.9.6.6 von ECMA-335 (Common Language Infrastructure (CLI) Partitionen I bis VI) heißt es in CLS-Regel 21:

CLS-Regel 21: Ein Objektkonstruktor muss einen Instanzkonstruktor Konstruktor seiner Basisklasse aufrufen, bevor ein Zugriff auf geerbte Instanzdaten erfolgt. (Dies gilt nicht für Wertetypen, die keine Konstruktoren haben.)

Dies bedeutet, dass vor alles sonst wird der Basiskonstruktor muss genannt werden. Wir können dies in der IL überprüfen. Die IL dafür würde wie folgt aussehen (ich habe die Byte-Werte in Klammern vor den IL-Befehl gesetzt):

// Loads "this" on the stack, as the first argument on an instance
// method is always "this".
(0x02) ldarg.0

// No parameters are loaded, but metadata token will be explained.
(0x28) call <metadata token>

Wir können nun damit beginnen, die Bytes daraufhin zu überprüfen:

    // Check the first two bytes, if they are not the loading of
    // the first argument and then a call, it's not
    // a call to a constructor.
    if (body[0] != 0x02 || body[1] != 0x28) return false;

Jetzt kommt das Metadaten-Token. Die call Anleitung erfordert die Übergabe eines Methodendeskriptors in Form eines Metadaten-Tokens zusammen mit dem Konstruktor. Dies ist ein Vier-Byte-Wert, der durch die MetadataToken Eigenschaft über die MemberInfo Klasse (wovon ConstructorInfo ableitet).

Wir könnte überprüfen, um zu sehen, dass das Metadaten-Token gültig war, aber da wir bereits die Länge des Byte-Arrays für den Methodenrumpf (mit sieben Bytes) überprüft haben und wir wissen, dass nur noch ein Byte übrig ist (die ersten beiden Op-Codes + vier Byte Metadaten-Token = sechs Bytes), müssen wir nicht überprüfen, ob es sich um einen parameterlosen Konstruktor handelt; wenn es Parameter gäbe, gäbe es andere Op-Codes, um die Parameter auf den Stack zu schieben und das Byte-Array zu erweitern.

Schließlich, wenn nichts anderes getan wird im Konstruktor (was bedeutet, dass der Compiler einen Konstruktor erzeugt hat, der nichts anderes tut, als die Basis aufzurufen), ein ret Anweisung würde nach dem Aufruf des Metadaten-Tokens ausgegeben:

(0x2A) ret

Das können wir folgendermaßen überprüfen:

    return body[6] == 0x2a;
}

Es ist zu beachten, warum die Methode genannt wird MightBeCSharpCompilerGenerated mit Schwerpunkt auf Macht .

Nehmen wir an, Sie haben die folgenden Klassen:

public class Base { }
public class Derived : Base { public Derived() { } }

Beim Kompilieren ohne Optimierungen (typischerweise DEBUG Modus), fügt der C#-Compiler ein paar nop Codes (vermutlich zur Unterstützung des Debuggers) für die Derived Klasse, was zu einem Aufruf von MightBeCSharpCompilerGenerated false zurückgeben.

Wenn jedoch die Optimierungen eingeschaltet sind (normalerweise, RELEASE Modus), gibt der C#-Compiler den sieben Byte langen Methodenkörper ohne die nop Opcodes, so dass es wie folgt aussehen wird Derived einen vom Compiler erzeugten Konstruktor hat, auch wenn er das nicht tut.

Aus diesem Grund heißt die Methode Might anstelle von Is o Has ; sie zeigt an, dass es könnte eine Methode sein, die Sie sich ansehen müssen, aber ich kann es nicht mit Sicherheit sagen. Mit anderen Worten: Sie werden nie ein falsches Negativ erhalten, aber Sie müssen trotzdem nachforschen, wenn Sie ein positives Ergebnis erhalten.

1 Stimmen

Was ist mit Feldinitialisierern? Sie werden in den Standard-Ctor eingefügt und können dazu führen, dass er größer als 7 Bytes ist.

0 Stimmen

@ravyoli Ein weiterer Grund, warum dies "Might" genannt wird, ist, dass die Bestimmung rein auf Heuristiken basiert. Die Antwort ist, dass es keinen deterministischen Weg gibt, dies zu tun.

5voto

David Nelson Punkte 3588

Es gibt keine Möglichkeit, automatisch generierte Standardkonstruktoren durch Metadaten zu erkennen. Sie können dies testen, indem Sie eine Klassenbibliothek mit zwei Klassen erstellen, eine mit einem expliziten Standardkonstruktor und eine ohne. Führen Sie dann ildasm für die Baugruppe aus: Die Metadaten der beiden Konstruktoren sind identisch.

Anstatt zu versuchen, generierte Konstruktoren zu erkennen, würde ich das Programm einfach so ändern, dass fehlende Dokumentation für jeden Standardkonstruktor zugelassen wird. Die meisten Dokumentationsprogramme, wie NDoc und SandcastleGUI, haben eine Option, um allen Standardkonstruktoren eine Standarddokumentation hinzuzufügen; es ist also wirklich nicht notwendig, sie überhaupt zu dokumentieren. Wenn Sie einen expliziten Standardkonstruktor in Ihrem Code haben, können Sie drei Schrägstriche (///) über den Konstruktor setzen - nichts anderes - um die Warnung von Visual Studio über fehlende Dokumentation zu deaktivieren.

0 Stimmen

Ich hatte befürchtet, dass dies die Antwort ist, und ich hatte gehofft, dass es möglich ist, weil StyleCop diese Situation korrekt behandelt.

1 Stimmen

StyleCop arbeitet mit dem ursprünglichen Quellcode, so dass es weiß, ob der Konstruktor implizit oder explizit war. Die meisten anderen statischen Code-Analyse-Tools und die meisten Dokumentationsprogramme arbeiten mit der kompilierten Baugruppe, bei der es unmöglich ist, den Unterschied zu erkennen.

0 Stimmen

Interessant. Man könnte eine Funktion beantragen, bei der die "automatischen" Standardkonstruktoren irgendwie dekoriert werden (mit einem Attribut).

1voto

Ben Aston Punkte 48965

Der folgende Code gibt Informationen über alle parameterlosen Konstruktoren in Ihrem Typ zurück:

var info = typeof(MyClass).GetConstructor(new Type[] {});

Mir ist keine Möglichkeit bekannt, zwischen einem Standardkonstruktor und einem explizit angegebenen parameterlosen Konstruktor zu unterscheiden.

Ein möglicher Workaround für Ihr CommentChecker-Problem wäre, den parameterlosen Konstruktor explizit zu erstellen, wo einer benötigt wird, und ihn entsprechend zu kommentieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X