During reflection, is it possible in C# to check whether one constructor calls another?
class Test
{
public Test() : this( false ) { }
public Test( bool inner ) { }
}
I would like to determine for each ConstructorInfo
whether or not it's at the end of chain of invocation.
Constructor chaining refers to the ability to call a constructor inside another constructor. You can use a constructor chain either within the same class or even with another one. For the latter, the constructor should be through inheritance from the super class.
From within a constructor, you can also use the this keyword to call another constructor in the same class. Doing so is called an explicit constructor invocation.
No, in C++ you cannot call a constructor from a constructor. What you can do, as warren pointed out, is: Overload the constructor, using different signatures. Use default values on arguments, to make a "simpler" version available.
To call one constructor from another constructor is called constructor chaining in java. This process can be implemented in two ways: Using this() keyword to call the current class constructor within the “same class”. Using super() keyword to call the superclass constructor from the “base class”.
This is a temporary answer, to state what I found so far.
I didn't find any property of ConstructorInfo
which could indicate whether the constructor calls another constructor or not. Neither did the properties of MethodBody
.
I am having somewhat success evaluating the MSIL byte code. My first findings indicate the constructor which is eventually called starts out with OpCodes.Call
immediately, except for a few possible other OpCodes
. Constructors which call other constructors have 'unexpected' 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.
);
}
I'm not sure at all about MSIL. Perhaps it's impossible to have no-ops in between there, or there is no need at all to start out a constructor like that, but for all my current unit tests it seems to work.
[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() );
}
}
}
Consider looking at Cecil or Roslyn.
Cecil operates on the compiled assembly, like Reflection does. it has higher-level libraries built on top of it to support refactorings in the SharpDevelop IDE, so it might have something to make this easier.
Roslyn operates on source code and gives you an object model based on that, so if you're willing to work against the source instead of binaries, it might be even easier to work with.
(I've never actually used Cecil for anything like this and I've never used Roslyn at all, so I can't do much more than point you at the projects and wish you luck. If you do manage to get something working, I'd be interested to hear how it went!)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With