2012-03-24 12 views
5

Durante la reflexión, ¿es posible en C# comprobar si un constructor llama a otro?Comprobar si un constructor llama a otro constructor

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

me gustaría determinar para cada ConstructorInfo si es o no está en el extremo de la cadena de invocación.

+0

¿Por qué necesitas hacer eso? La llamada al otro constructor se compila como una llamada a método normal, por lo que creo que necesitarás leer el IL del método para hacerlo. – svick

+0

@svick Estoy [aplicando aspectos] (http://www.sharpcrafters.com/), y me gustaría encontrar el constructor final al que se llamaría para aplicar el aspecto. –

+1

Considere la posibilidad de mirar [Cecil] (http://www.mono-project.com/Cecil) o [Roslyn] (http://msdn.microsoft.com/en-us/roslyn). Cecil opera en el ensamblado compilado, como Reflection, pero tiene bibliotecas de alto nivel construidas encima para soportar refactorizaciones en el IDE de SharpDevelop, por lo que podría tener algo que lo haga más fácil.Roslyn opera con el código fuente y le da un modelo de objeto basado en eso, por lo que si está dispuesto a trabajar contra la fuente en lugar de los binarios, podría ser aún más fácil. –

Respuesta

1

Considere la posibilidad de mirar Cecil o Roslyn.

Cecil opera en el ensamblado compilado, como Reflection does. tiene bibliotecas de alto nivel construidas encima para soportar refactorizaciones en el IDE de SharpDevelop, por lo que podría tener algo que lo haga más fácil.

Roslyn opera en código fuente y le proporciona un modelo de objeto basado en eso, por lo que si está dispuesto a trabajar en contra de la fuente en lugar de binarios, podría ser aún más fácil trabajar con ella.

(Nunca he usado Cecil para nada como esto y nunca he usado Roslyn en absoluto, así que no puedo hacer mucho más que señalarte en los proyectos y te deseo suerte. Si logras obtener algo que funcione, ¡me interesaría saber cómo fue!)

+0

He aceptado esta respuesta, ya que es probablemente un mejor enfoque que el que publiqué. Sin embargo, una palabra de advertencia: ** ¡Todavía no he intentado hacerlo! ** –

3

Esta es una respuesta temporal, para indicar lo que encontré hasta ahora.

No he encontrado ninguna propiedad de ConstructorInfo que pueda indicar si el constructor llama a otro constructor o no. Tampoco las propiedades de MethodBody.

Estoy teniendo un poco de éxito evaluando el código de bytes de MSIL. Mis primeros hallazgos indican que el constructor que finalmente se llama comienza con OpCodes.Call inmediatamente, a excepción de algunos otros posibles OpCodes. Los constructores que llaman a otros constructores tienen 'inesperado' 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. 
     ); 
} 

No estoy seguro de nada sobre MSIL. Tal vez es imposible no tener ninguna operación intermedia entre ellos, o no hay ninguna necesidad de comenzar un constructor como ese, pero para todas mis pruebas de unidades actuales parece funcionar.

[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

Un recurso útil de [todos los códigos de bytes se puede encontrar aquí] (http://blogs.msdn.com/b/bluecollar/archive/2006/09/27/773065.aspx). –

+0

Los constructores que llaman a constructores base parecen tener un valor 'OpCodes.Ldarg_1' después de' OpCodes.Ldarg_0'. –

+0

La versión actualizada no parece funcionar aún para los constructores de clase interna. –

0

Por lo que yo sé, no se puede verificar o revisar el código utilizando la reflexión de una manera fácil. Toda la reflexión que le permite hacer es reflejar en la información de metadatos del conjunto.

Puede usar GetMethodBody para captar el contenido del método, pero luego tendrá que analizarlo y comprender la IL usted mismo.

1

lo que puede hacer es agregar una propiedad al objeto que indica que se aplicó el aspecto. Por lo tanto, no aplicará el aspecto varias veces ya que puede verificar esa propiedad. No es lo que pediste, pero puede ayudarte con tu problema subyacente.

+1

Eso no garantiza que el código de aspecto se ejecute para cada posible invocación de constructor. A menos que esté implicando que el código que agregué a _every_constructor es consciente de una propiedad que hace que solo se ejecute una vez. Aquello podría funcionar. –

+0

sí, esa es la idea – ivowiblo

Cuestiones relacionadas