Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check whether a constructor calls another constructor

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.

like image 658
Steven Jeuris Avatar asked Mar 24 '12 03:03

Steven Jeuris


People also ask

Can a constructor call another constructor?

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.

Can a constructor call another constructor using this keyword in java?

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.

Can a constructor call another constructor C++?

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.

Can constructors call each other?

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”.


2 Answers

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() );
        }               
    }
}
like image 157
Steven Jeuris Avatar answered Sep 25 '22 14:09

Steven Jeuris


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!)

like image 32
Joe White Avatar answered Sep 22 '22 14:09

Joe White