如何将 Predicate<T> 转换为 Expression<Predicate<T>> 以在 Moq 中使用?

6

请帮助这位Linq新手!

我正在创建一个测试类中的列表,我想使用Moq来检查结果。

我可以很容易地编写一个谓词来检查列表的结果。如何将该谓词转换为表达式?

var myList = new List<int> {1, 2, 3};

Predicate<List<int>> myPredicate = (list) => 
                  {
                      return list.Count == 3; // amongst other stuff
                  };

// ... do my stuff

myMock.Verify(m => m.DidStuffWith(It.Is<List<int>>( ??? )));

如果你能理解这么多泛型,那么需要一个Expression<Predicate<List<int>>>表达式。我找到的答案是将表达式编译成谓词,但这并没有帮助我更好地理解Linq。

编辑:我已经用方法使其工作了;对于表达式,我只想知道是否有任何方法可以使用带有主体的lambda表达式来实现-如果没有,为什么没有?


这样做和让myMock.DidStuffWith(...)返回被要求进行操作的列表并在本地进行检查之间有什么功能上的区别? - Matt Mills
@arootbeer 改变事物并返回值的方法最终被用于返回值,而改变事物成为了可怕的副作用。看到过太多次了。要么改变状态或者返回值,但不要两者都做(除了流畅接口的情况,这些接口是专门为返回接口而设计的)。从功能上讲没有区别,但在面向对象编程上有区别。 - Lunivore
所以,myMock.DidStuffWith(...) 是一个 void 方法吗?如果是的话,那我完全同意你的观点。 - Matt Mills
@arootbeer 是的,它是(实际上是存储库中的保存操作)。 - Lunivore
骗局。我会添加一个更方便处理它的答案。 - Matt Mills
2个回答

5

更改:

Predicate<List<int>> myPredicate = (list) => list.Count == 3;

致:

Expression<Predicate<List<int>>> myPredicate = (list) => list.Count == 3;

编译器将为您执行魔术。 除了一些警告1之外,任何只涉及表达式(没有块)的lambda都可以通过使用Expression<>包装委托类型(在此情况下为Predicate<List<int>>)将其转换为表达式树。 正如您所指出的那样,然后可以通过调用myPredicate.Compile()再次进行反转。

1例如,异步lambda(即async () => await Task.Delay(1);)不能转换为表达式树。

更新: 如果表达式树包含语句,则您无法使用编译器来获得所需的表达式树。 您需要使用Expression中的静态方法自己构建表达式树(更多工作)。 (Expression.BlockExpression.IfThen等)


这适用于表达式,但不适用于带有表达式主体的lambda。已更新以包括在我的示例中。谢谢你教我那一点; 我没有尝试过不使用括号。 - Lunivore
1
@Kirk Woll:“任何lambda都可以转换为表达式树。”这是错误的。考虑具有表达式主体的lambda。 - jason
有人能解释一下,表达式树的本质是什么导致了这种情况吗?还是说它们只是编译成了某些可怕(或美丽)的函数式代码,而函数不能包含状态? - Lunivore
@Lunivore,请参见Eric Lippert在这里的回答:https://dev59.com/f0nSa4cB1Zd3GeqPNV8a:“这是一个明显且有用的功能,但我们在C# 4中没有时间去实现。我们会考虑在未来的版本中实现它。如果您有真正伟大的声明树用户场景,我很乐意听取。” - Kirk Woll
@Kirk Woll,谢谢。我刚刚更新了Eric的答案,因为这显然是一个非常好的用户场景。 - Lunivore

1
Kirk Woll的回答直接回应了你的问题,但请考虑一下这样一个事实:你可以在void方法的Setup上使用Callback方法来处理调用时传递的参数。对我来说,这更有意义,因为你已经不得不构建一个验证列表的方法;它还给你一个本地副本的列表。
//what is this list used for?
var myList = new List<int> {1, 2, 3};

List<int> listWithWhichStuffWasDone = null;
//other setup

myMock.Setup(m => m.DoStuffWith(It.IsAny<List<int>>()).
    Callback<List<int>>(l => listWithWhichStufFWasDone = l);

objectUnderTest.MethodUnderTest();

myMock.Verify(m => m.DoStuffWith(It.IsAny<List<int>>()));
Validate(listWithWhichStuffWasDone);

如果我要设置一个方法,那当然可以使用Setup。但是,我正在使用Verify。我不喜欢整个Given、Expect、When、Then的过程,因为你想要的结果可能会在中间某个奇怪的地方发生,所以Setup并不能解决问题。我更喜欢测试的结果出现在测试的结尾处,这样更易读。 - Lunivore
我用了一个方法解决了这个问题。Eric也向我表示了感谢,所以我也要感谢你! - Lunivore
有趣的是,我从Rhino Mocks(晚期2.x或早期3.x,如果我没记错的话)转到了Moq,那里你必须显式地设置所有内容。我注意到在Moq中你不必这样做,但我从未考虑过省略安排部分,只是在最后进行断言。 - Matt Mills

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接