C# 使用NSubstitute的LINQPad中的奇怪行为

C# 使用NSubstitute的LINQPad中的奇怪行为,c#,linqpad,nsubstitute,C#,Linqpad,Nsubstitute,我在LINQPad 4(v.4.57.02)中编写了以下示例,以演示试图使用NSubstitute(v1.9.2.0)模拟类型及其非虚拟属性的愚蠢行为: void Main() { var foo = Substitute.For<Foo>(); foo.Alarm.Returns(2); foo.Alarm.Dump(); } public class Foo { public Foo() { Console.Wri

我在LINQPad 4(
v.4.57.02
)中编写了以下示例,以演示试图使用NSubstitute(
v1.9.2.0
)模拟类型及其非虚拟属性的愚蠢行为:

void Main()
{
    var foo = Substitute.For<Foo>();
    foo.Alarm.Returns(2);    
    foo.Alarm.Dump();
}

public class Foo
{
    public Foo()
    {
        Console.WriteLine("Foo ctor called.");
    }

    public virtual int Alarm
    {
        get; set;
    }
}
现在,当我编辑代码以删除
报警
属性上的
虚拟
修饰符时,我希望看到一个
NSubstitute.Exceptions.CouldNotSetReturnDueToNoLastCallException
异常,其中包括:

如果替换了类而不是接口,请检查对替换对象的调用是否在虚拟/抽象成员上。无法为非虚拟/非抽象成员配置返回值

但是,我第一次运行修改后的代码时得到:

Foo ctor called.
0
在随后的运行中,我得到了预期的异常


现在我怀疑LINQPad管理AppDomains的方式和NSubstitute的Castle代理的工作方式有点滑稽,但我不知道是什么。举起手来,我只是没时间深入研究,并且想知道是否有其他人有明确的解释,因为了解LINQPad执行环境中的gotchas会让人感到安慰。

如果您开始打开一个新的LINQPad实例,并在没有
虚拟
成员的情况下运行代码,它将立即失败,并出现预期的错误

这是我对正在发生的事情的猜测。第一次使用
virtual
成员NSubstitute的状态运行代码时如下所示:

var foo = Substitute.For<Foo>();
foo.Alarm         // 1. last call is foo.Alarm
   .Returns(2);   // 2. make foo.Alarm return `2`. Clear last call.
foo.Alarm         // 3. last call is foo.Alarm
   .Dump();       // 4. extension method -- doesn't clear last call
var foo=Substitute.For();
foo.Alarm//1。最后一个电话是foo.Alarm
.返回(2);//2.使foo.Alarm返回'2'。清除最后一个呼叫。
foo.Alarm//3。最后一个电话是foo.Alarm
.Dump();//4.扩展方法--不清除上次调用

NSSubstitute静态存储对替换的最后一次调用,因此它将一直挂起,直到appdomain消失。当您修改代码以删除
虚拟
并再次运行它时,步骤2中的
.Returns(2)
会找到在上一次运行的步骤3中进行的最后一次调用,相应地将其存根,然后清除最后一次调用。由于非虚拟成员的原因,不会记录进一步的调用,因此后续运行会失败,并出现预期错误。

这当然很有意义。不错。另外,需要注意的是,上一次运行的调用是在stubbed对象的另一个实例上进行的,这意味着
.Returns(2)
调用是在一个对象上进行的,该对象不再可以从LINQPad代码访问,而是可以从NSubstitute中“last call”概念的静态存储中访问。
var foo = Substitute.For<Foo>();
foo.Alarm         // 1. last call is foo.Alarm
   .Returns(2);   // 2. make foo.Alarm return `2`. Clear last call.
foo.Alarm         // 3. last call is foo.Alarm
   .Dump();       // 4. extension method -- doesn't clear last call