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());
}
}
}
¿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
@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. –
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. –