5

在反射期间,是否可以在 C# 中检查一个构造函数是否调用另一个构造函数?

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

我想确定每个ConstructorInfo是否在调用链的末尾。

4

4 回答 4

3

这是一个临时答案,以说明我到目前为止的发现。

我没有找到任何ConstructorInfo可以指示构造函数是否调用另一个构造函数的属性。的属性也没有MethodBody

我在评估 MSIL 字节码方面取得了一些成功。我的第一个发现表明最终被调用的构造函数OpCodes.Call立即开始,除了一些可能的 other OpCodes。调用其他构造函数的构造函数有 '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.
        );
}

我完全不确定 MSIL。也许在两者之间不可能有无操作,或者根本不需要启动这样的构造函数,但对于我当前的所有单元测试,它似乎都有效。

[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() );
        }               
    }
}
于 2012-03-24T03:58:04.813 回答
1

您可以做的是向对象添加一个属性,告诉该方面已应用。因此,您不会多次应用方面,因为您可以检查该属性。这不是您所要求的,但它可能会帮助您解决潜在的问题。

于 2012-03-24T04:07:15.807 回答
1

考虑看看CecilRoslyn

Cecil 对编译后的程序集进行操作,就像 Reflection 一样。它在其之上构建了更高级别的库,以支持 SharpDevelop IDE 中的重构,因此它可能有一些东西可以让这更容易。

Roslyn 对源代码进行操作,并为您提供基于此的对象模型,因此如果您愿意使用源代码而不是二进制文件,那么使用它可能会更容易。

(我从来没有真正使用过 Cecil 来做这样的事情,我也从来没有使用过 Roslyn,所以我只能指点你的项目并祝你好运。如果你确实设法让某些东西发挥作用,我很想知道它是怎么回事!)

于 2012-03-25T03:34:04.107 回答
0

据我所知,您无法以简单的方式使用反射检查或检查代码。您所做的所有反射都是反射程序集的元数据信息。

您可以使用GetMethodBody来获取方法的内容,但是您必须自己实际解析它并理解 IL。

于 2012-03-24T03:58:32.003 回答