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