【问题标题】:Assert specific constructor called by child class断言子类调用的特定构造函数
【发布时间】:2017-04-04 09:09:56
【问题描述】:

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

看起来像这样

public abstract class EntityBase {
    public Guid ID {get; protected set;}

    protected EntityBase() { }

    public EntityBase(Guid id) { ID = id }

    public static Guid NewID => GenerateGuid();
}

public class Entity : EntityBase {
    public int X {get; set;}

    protected Entity() { }

    public Entity(int x) : base(NewID) { X = x; }
}

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

public EntityBase(Guid id) { ID = id }

目前,我的代码循环遍历所有可从 EntityBase 分配的具体类的构造函数,但我不知道如何进行最终检查。对解决方案的研究建议尝试使用反射来读取 IL。我考虑过尝试检查是否调用了“NewID”,但也找不到任何完成该操作的方法。

有没有办法实现这一点,或者我对 NHibernate 问题的解决方案是真正的问题?

【问题讨论】:

  • 派生类实例化后能否更改ID?如果没有,为什么不将其更改为私有集,然后检查它是否已设置?还是您的问题比这更复杂?嗯,我猜这是单元测试,有人可以轻松地将“受保护”更改为“公共”......
  • 不,实例化后无法更改。我想编写一个单元测试来强制执行正确的构造函数行为。如果我尝试调用每个构造函数并检查是否设置了 ID 字段,那么我需要找到某种方法来为每个可能的当前和未来构造函数实例化一组未知的参数。此外,如果子类的构造函数使用其参数的属性,那么我需要确保这些字段也被填充。

标签: c# nhibernate reflection constructor


【解决方案1】:

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

开始之前的一个警告:一些 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);

【讨论】:

  • 感谢您的努力,john - 这肯定会让我开始。我将此标记为正确答案。对于其他查看此内容的人,此 sn-p 不适用于嵌套的基本构造函数。需要明确的是,如果我们引入第三个实体 Entity2 : Entity,并在 Entity2 的构造函数上调用 use this,它不会检测到 EntityBase 的构造函数。您将需要递归遍历类层次结构。
  • 这也不适用于在移动到基本构造函数之前使用“this”引用同一类中的其他构造函数
  • 我添加了一个编辑来纠正您对操作码大小的重复计算。
  • @Derek 哦,哎呀!很高兴我能帮忙:)
【解决方案2】:

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

【讨论】:

  • 嗯,我只是在研究一个符合 Mark Cidade 建议 #1 的答案 :)
猜你喜欢
  • 2015-03-15
  • 1970-01-01
  • 1970-01-01
  • 2018-02-28
  • 2021-05-30
  • 2014-02-19
  • 2018-05-25
  • 1970-01-01
相关资源
最近更新 更多