断言子类调用的特定构造函数

时间:2017-03-29 02:29:10

标签: c# nhibernate reflection constructor

我正在尝试编写一个测试来验证子类是否调用了特定的基类'构造函数。实际上,当服务器实例化对象时,会生成我的实体的ID。我使用NHibernate作为我的ORM,所以我需要一个0参数构造函数。我希望这个构造函数不会在每次NHibernate水合实体时生成Guid,所以我为我的基本实体创建了第二个构造函数,其中Guid作为参数。

它看起来像这样

SELECT b.hotelNo,g.guestName,b.dateFrom,b.dateTo 
FROM Booking b, Guest g 
WHERE b.guestNo = g.guestNo
GROUP BY b.hotelNo,g.guestName 
HAVING COUNT(*) > 1;

我想编写的这个测试应断言所有EntityBase的具体子类的构造函数(除了0参数构造函数)都调用正确的基础构造函数:

  

public EntityBase(Guid id){ID = id}

目前,我的代码在可从EntityBase分配的所有具体类上循环构造函数,但我不知道如何进行最终检查。研究解决方案建议尝试使用反射读取IL。我考虑过要检查一下是否有新的ID'已被召唤,但无法找到任何成就。

有没有办法实现这一目标,还是我对NHibernate问题的解决方案是真正的问题?

2 个答案:

答案 0 :(得分:2)

您需要detect which base constructor is called by each constructor,检查no-args构造函数是否仅由no-args非公共构造函数调用,并在类层次结构上递归执行直到您的基类。

答案 1 :(得分:2)

好的,所以这花了我一段时间,但我喜欢挑战。

在我们开始之前的一个警告:某些OperandType值在字节大小方面没有在MSDN上列出,所以我不能确定这是正确解析所有内容。

传入派生的和基本的构造函数,然后如果已经调用了构造函数,它就可以运行。

public bool IsCalled(ConstructorInfo derivedConstructor, ConstructorInfo baseConstructor)
{
    var body = derivedConstructor.GetMethodBody();
    var expectedConstructorToken = baseConstructor.MetadataToken;
    byte[] il = body.GetILAsByteArray();

    var codes = new Dictionary<short, OpCode>();
    var fields = typeof(OpCodes).GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
    foreach (var field in fields)
    {
        var value = field.GetValue(null);
        if (!(value is OpCode)) { continue; }
        var opCode = (OpCode)value;
        codes.Add(opCode.Value, opCode);
    }

    var operandSizes = new Dictionary<OperandType, int>();
    // https://msdn.microsoft.com/en-us/library/system.reflection.emit.operandtype(v=vs.110).aspx
    operandSizes.Add(OperandType.InlineBrTarget, 4);
    operandSizes.Add(OperandType.InlineField, 4);
    operandSizes.Add(OperandType.InlineI, 4);
    operandSizes.Add(OperandType.InlineI8, 8);
    operandSizes.Add(OperandType.InlineMethod, 4);
    operandSizes.Add(OperandType.InlineNone, 0);
    operandSizes.Add(OperandType.InlineR, 8);
    operandSizes.Add(OperandType.InlineSig, 4);
    operandSizes.Add(OperandType.InlineString, 4);
    operandSizes.Add(OperandType.InlineSwitch, 4);
    operandSizes.Add(OperandType.InlineType, 32);
    operandSizes.Add(OperandType.InlineVar, 2);
    operandSizes.Add(OperandType.ShortInlineBrTarget, 1);
    operandSizes.Add(OperandType.ShortInlineI, 1);
    operandSizes.Add(OperandType.ShortInlineR, 4);
    operandSizes.Add(OperandType.ShortInlineVar, 1);

    var i = 0;
    while(i < il.Length) {
        OpCode operation = OpCodes.Nop;
        if (il[i] == 0xfe)
        {
            operation = codes[BitConverter.ToInt16(il, i)];
        }
        else
        {
            operation = codes[(short)il[i]];
        }
        i += operation.Size;

        if (operation.OperandType == OperandType.InlineMethod)
        {
            var token = BitConverter.ToInt32(il, i);
            if (token == expectedConstructorToken) { return true; }
        }

        i += operandSizes[operation.OperandType];
    }
    return false;
}

用法:

var derivedConstructor = typeof(Derived).GetConstructor(new Type[] { typeof(int) });
var baseConstructor = typeof(Base).GetConstructor(new Type[] { typeof(int) });
bool isCalled = IsCalled(derivedConstructor, baseConstructor);