2012-03-24 6 views
5

Während der Reflektion ist es in C# möglich zu prüfen, ob ein Konstruktor einen anderen aufruft?Überprüfen, ob ein Konstruktor einen anderen Konstruktor aufruft

class Test 
{ 
    public Test() : this(false) { } 
    public Test(bool inner) { }  
} 

Ich möchte für jeden ConstructorInfo, um zu bestimmen, ob es am Ende der Kette des Aufrufs ist.

+0

Warum müssen Sie das tun? Der Aufruf an den anderen Konstruktor wird als normaler Methodenaufruf kompiliert, daher denke ich, dass Sie dazu die IL der Methode lesen müssen. – svick

+0

@svick Ich bin [Aspekte anwenden] (http://www.sharpcrafters.com/), und ich möchte den finalen Konstruktor finden, der aufgerufen werden würde, um den Aspekt anzuwenden. –

+1

Betrachten Sie [Cecil] (http://www.mono-project.com/Cecil) oder [Roslyn] (http://msdn.microsoft.com/en-us/roslyn). Cecil arbeitet wie Reflection mit der kompilierten Assembly, hat aber darüber hinaus Bibliotheken auf höherer Ebene, um Refactorings in der SharpDevelop-IDE zu unterstützen, damit es etwas leichter zu machen ist.Roslyn arbeitet mit Quellcode und gibt Ihnen ein darauf basierendes Objektmodell. Wenn Sie also bereit sind, gegen die Quelle statt gegen Binärdateien zu arbeiten, ist es vielleicht noch einfacher. –

Antwort

1

Betrachten Sie Cecil oder Roslyn betrachten.

Cecil arbeitet auf der kompilierten Assembly, wie Reflection tut. Es hat darüber hinaus Bibliotheken auf höherer Ebene, um Refactorings in der SharpDevelop-IDE zu unterstützen, also könnte es etwas leichter machen.

Roslyn arbeitet mit Quellcode und gibt Ihnen ein darauf basierendes Objektmodell. Wenn Sie also bereit sind, gegen die Quelle statt gegen Binärdateien zu arbeiten, ist es möglicherweise noch einfacher, damit zu arbeiten.

(Ich habe Cecil nie für so etwas benutzt und Rosylyn habe ich nie benutzt, also kann ich nicht viel mehr tun, als auf die Projekte zu verweisen und Ihnen Glück zu wünschen. Wenn Sie es schaffen Wenn etwas funktioniert, wäre es interessant zu hören, wie es gelaufen ist!)

+0

Ich habe diese Antwort akzeptiert, da es wahrscheinlich eine bessere Herangehensweise ist als die, die ich gepostet habe. Ein Wort der Warnung, aber ich habe es noch nicht probiert! –

3

Dies ist eine vorläufige Antwort, um zu sagen, was ich bisher gefunden habe.

Ich habe keine Eigenschaft von ConstructorInfo gefunden, die anzeigen könnte, ob der Konstruktor einen anderen Konstruktor aufruft oder nicht. Weder haben die Eigenschaften von MethodBody.

Ich habe etwas Erfolg beim Bewerten des MSIL-Byte-Codes. Meine ersten Ergebnisse zeigen, dass der Konstruktor, der schließlich aufgerufen wird, sofort mit OpCodes.Call beginnt, bis auf einige mögliche andere OpCodes. Konstruktoren, die andere Konstruktoren aufrufen, haben "unerwartet" OpCodes.

public static bool CallsOtherConstructor(this ConstructorInfo constructor) 
{ 
    MethodBody body = constructor.GetMethodBody(); 
    if (body == null) 
    { 
     throw new ArgumentException("Constructors are expected to always contain byte code."); 
    } 

    // Constructors at the end of the invocation chain start with 'call' immediately. 
    var untilCall = body.GetILAsByteArray().TakeWhile(b => b != OpCodes.Call.Value); 
    return !untilCall.All(b => 
     b == OpCodes.Nop.Value ||  // Never encountered, but my intuition tells me a no-op would be valid. 
     b == OpCodes.Ldarg_0.Value || // Seems to always precede Call immediately. 
     b == OpCodes.Ldarg_1.Value // Seems to be added when calling base constructor. 
     ); 
} 

Ich bin überhaupt nicht sicher über MSIL. Vielleicht ist es unmöglich, dazwischen eine No-Op-Operation zu haben, oder es ist gar nicht nötig, einen solchen Konstruktor zu starten, aber für alle meine aktuellen Unit-Tests scheint es zu funktionieren.

[TestClass] 
public class ConstructorInfoExtensionsTest 
{ 
    class PublicConstructors 
    { 
     // First 
     public PublicConstructors() : this(true) {} 

     // Second 
     public PublicConstructors(bool one) : this(true, true) {} 

     // Final 
     public PublicConstructors(bool one, bool two) {} 

     // Alternate final 
     public PublicConstructors(bool one, bool two, bool three) {} 
    } 

    class PrivateConstructors 
    { 
     // First 
     PrivateConstructors() : this(true) {} 

     // Second 
     PrivateConstructors(bool one) : this(true, true) {} 

     // Final 
     PrivateConstructors(bool one, bool two) {} 

     // Alternate final 
     PrivateConstructors(bool one, bool two, bool three) {} 
    } 

    class TripleBaseConstructors : DoubleBaseConstructors 
    { 
     public TripleBaseConstructors() : base() { } 
     public TripleBaseConstructors(bool one) : base(one) { } 
    } 

    class DoubleBaseConstructors : BaseConstructors 
    { 
     public DoubleBaseConstructors() : base() {} 
     public DoubleBaseConstructors(bool one) : base(one) {} 
    } 

    class BaseConstructors : Base 
    { 
     public BaseConstructors() : base() {} 
     public BaseConstructors(bool one) : base(one) {} 
    } 

    class Base 
    { 
     // No parameters 
     public Base() {} 

     // One parameter 
     public Base(bool one) {} 
    } 

    class ContentConstructor 
    { 
     public ContentConstructor() 
     { 
      SomeMethod(); 
     } 

     public ContentConstructor(bool one) 
     { 
      int bleh = 0; 
     } 

     bool setTwo; 
     public ContentConstructor(bool one, bool two) 
     { 
      setTwo = two; 
     } 

     void SomeMethod() {} 
    } 

    [TestMethod] 
    public void CallsOtherConstructorTest() 
    {   
     Action<ConstructorInfo[]> checkConstructors = cs => 
     { 
      ConstructorInfo first = cs.Where(c => c.GetParameters().Count() == 0).First(); 
      Assert.IsTrue(first.CallsOtherConstructor()); 
      ConstructorInfo second = cs.Where(c => c.GetParameters().Count() == 1).First(); 
      Assert.IsTrue(second.CallsOtherConstructor()); 
      ConstructorInfo final = cs.Where(c => c.GetParameters().Count() == 2).First(); 
      Assert.IsFalse(final.CallsOtherConstructor()); 
      ConstructorInfo alternateFinal = cs.Where(c => c.GetParameters().Count() == 3).First(); 
      Assert.IsFalse(alternateFinal.CallsOtherConstructor()); 
     }; 

     // Public and private constructors. 
     checkConstructors(typeof(PublicConstructors).GetConstructors()); 
     checkConstructors(typeof(PrivateConstructors).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)); 

     // Inheritance. 
     Action<ConstructorInfo[]> checkBaseConstructors = cs => 
     { 
      ConstructorInfo noParameters = cs.Where(c => c.GetParameters().Count() == 0).First(); 
      ConstructorInfo oneParameter = cs.Where(c => c.GetParameters().Count() == 1).First(); 

      // Only interested in constructors specified on this type, not base constructors, 
      // thus calling a base constructor shouldn't qualify as 'true'. 
      Assert.IsFalse(noParameters.CallsOtherConstructor()); 
      Assert.IsFalse(oneParameter.CallsOtherConstructor()); 
     }; 
     checkBaseConstructors(typeof(BaseConstructors).GetConstructors()); 
     checkBaseConstructors(typeof(DoubleBaseConstructors).GetConstructors()); 
     checkBaseConstructors(typeof(TripleBaseConstructors).GetConstructors()); 

     // Constructor with content. 
     foreach(var constructor in typeof(ContentConstructor).GetConstructors()) 
     { 
      Assert.IsFalse(constructor.CallsOtherConstructor()); 
     }    
    } 
} 
+0

Eine nützliche Ressource von [alle Byte-Codes finden Sie hier] (http://blogs.msdn.com/b/bluecollar/archive/2006/09/27/773065.aspx). –

+0

Konstruktoren, die Basiskonstruktoren aufrufen, scheinen nach 'OpCodes.Ldarg_0' einen' OpCodes.Ldarg_1' Wert zu haben. –

+0

Die aktualisierte Version scheint noch nicht für innere Klassenkonstruktoren zu funktionieren. –

0

Soweit ich weiß, kann man nicht Code überprüfen oder überprüfen Reflexion auf einfache Art und Weise mit. Bei allen Überlegungen können Sie die Metadateninformationen der Baugruppe reflektieren.

Sie können GetMethodBody verwenden, um den Inhalt der Methode zu greifen, aber dann müssen Sie es tatsächlich analysieren und die IL selbst verstehen.

1

Sie können dem Objekt eine Eigenschaft hinzufügen, die besagt, dass der Aspekt angewendet wurde. Sie werden den Aspekt also nicht mehrmals anwenden, da Sie diese Eigenschaft überprüfen können. Es ist nicht das, was Sie gefragt haben, aber es kann Ihnen bei Ihrem zugrunde liegenden Problem helfen.

+1

Das würde nicht garantieren, dass der Aspektcode für jeden möglichen Konstruktoraufruf ausgeführt würde. Wenn Sie nicht andeuten, dass der Code, den ich zu _every_ constructor hinzufüge, eine Eigenschaft kennt, die bewirkt, dass sie nur einmal ausgeführt wird? Das könnte funktionieren. –

+0

Yep, das ist die Idee – ivowiblo

Verwandte Themen