CallTo 在循环中不起作用

CallTo does not work in a loop

我正在尝试创建一个伪造的对象缓存 class 以用于测试,但我 运行 出现了奇怪的行为。

我是这样开始的:

IObjectCache objectCache = A.Fake<IObjectCache>();

List<ICachedObject> objects = new List<ICachedObject>();

ICachedObject object1 = A.Fake<ICachedObject>();
A.CallTo(() => object1.Id).Returns(random.Next());
objects.Add(object1);

//Snip: Identical statements for object2 & object3...

A.CallTo(objectCache)
    .Where(call => call.Method.Name == "get_Item")
    //"get_Item" is the generated name for the indexer []
    .WithReturnType<ICachedObject>()
    .WhenArgumentsMatch((int objectId) => objectId.Equals(object1.Id))
    .Returns(object1);

//Snip: Identical statements for object2 & object3...

//The lookup works as expected:
var x = objectCache[object1.Id]; //returns object1
var y = objectCache[object2.Id]; //returns object2
var z = objectCache[object3.Id]; //returns object3

在开始工作后,我想通过将 "CallTo" 语句移动到循环中来稍微清理一下测试。但是,当我这样做时,假货不再 returns 预期的对象。

foreach (ICachedObject obj in objects)
{
    A.CallTo(objectCache)
        .Where(call => call.Method.Name == "get_Item")
        //"get_Item" is the generated name for the indexer []
        .WithReturnType<ICachedObject>()
        .WhenArgumentsMatch((int objectId) => objectId.Equals(obj.Id))
        .Returns(obj);
}

//Only the last object "added" works
var x = objectCache[object1.Id]; //returns a new, empty object
var y = objectCache[object2.Id]; //returns a new, empty object
var z = objectCache[object3.Id]; //returns object3

我想知道 WhenArgumentsMatch 和 Returns 的参数是否在被索引器查找调用之前不会被评估并且只能保存一个值。还有其他方法吗?

我无法重现您的问题,但我强烈怀疑...您使用的是 C# 4 或更早版本吗?在 C# 5 之前,foreach 循环中的循环变量逻辑上在 外部 循环中声明,因此如果您在 lambda 中捕获它,您总是引用同一个变量。这意味着当您的 lambda (int objectId) => objectId.Equals(obj.Id) 被评估时,obj 总是引用列表中的最后一项。此行为在 C# 5 中已更改,因此循环变量在循环 内部 中逻辑声明,从而防止这种令人惊讶的行为(有关详细信息,请参阅 Eric Lippert 的 this article)。

如果这确实是您问题的原因,只需在循环中复制 obj

foreach (ICachedObject obj in objects)
{
    var copy = obj;
    A.CallTo(objectCache)
        .Where(call => call.Method.Name == "get_Item")
        //"get_Item" is the generated name for the indexer []
        .WithReturnType<ICachedObject>()
        .WhenArgumentsMatch((int objectId) => objectId.Equals(copy.Id))
        .Returns(copy);
}

顺便说一句,您可以更轻松地配置这些调用:

foreach (var obj in objects)
{
    var copy = obj;
    A.CallTo(() => objectCache[copy.Id]).Returns(copy);
}