【问题标题】:Projected / enumerated array returns last element copied to all other elements投影/枚举数组返回复制到所有其他元素的最后一个元素
【发布时间】:2014-01-03 03:45:23
【问题描述】:

我不确定这是否是起订量问题,或者我做了一些显而易见的事情,而且我已经研究了太久了。

通过以下语句,我得到了一些非常奇怪的结果:

var orderedFiles = files
    .Select(p => {
        var post = _serializer.Deserialize<BlogPostModel>(_fs.File.ReadAllText(p));
        post.FileDate = GetFileNameDate(p);
        post.FilePath = p;
        return post;
    })
    .OrderByDescending(x => x.FileDate);

当我枚举这个时,(通过使用 orderedFiles.ToArray() 或 .ToList())我将返回原始“文件”数组中的最后一个元素,返回可枚举中的所有元素。所以,对于下面的测试,我得到了预计的 file5 值,5 次。

当我在 Select() 方法调用中放置断点时,每次迭代都会返回正确的值。在我枚举之后,值都是一样的——截图如下:

上图:Select() 正确返回了投影值——每次迭代都返回一个完全不同的对象。

上图:枚举值都等于原始 GetFiles() 调用的最后一个元素,您可以通过 Locals 窗口中的两个展开元素看到。

这是我的测试(调用 GetService() 时将模拟插入到服务中,并在 nunit SetUp 方法中放置了许多全局设置):

[Test]
public void GetBlogPosts_PicksUpFilesInReverseFileNameDateOrder() {
    // Arrange
    var file1 = Path.Combine(blogPathContent, "2013-12-28-04-31-41-this-is-the-blog-title-1.json"); // 2
    var file2 = Path.Combine(blogPathContent, "2013-10-09-01-54-43-this-is-the-blog-title-2.json"); // 3
    var file3 = Path.Combine(blogPathContent, "2014-01-12-18-52-32-this-is-the-blog-title-3.json"); // 1
    var file4 = Path.Combine(blogPathContent, "2012-12-20-06-18-23-this-is-the-blog-title-5.json"); // 5
    var file5 = Path.Combine(blogPathContent, "2013-06-04-12-28-56-this-is-the-blog-title-4.json"); // 4

    mockSerializer
        .Setup(x => x.Deserialize<BlogPostModel>(It.IsAny<string>()))
        .Returns(new BlogPostModel());

    mockDirectory
        .Setup(x => x.GetFiles(It.Is<string>(s => s == blogPathContent),
            It.Is<string>(s => s == string.Concat("*", blogFilesExt))))
        .Returns(new[] {file1, file2, file3, file4, file5});

    service = GetService();

    // Act
    var actualResult = service.GetBlogPosts(new GetBlogPostsRequest());

    // Assert
    //mockFile1.VerifyGet(x => x.FullName, Times.Exactly(5), "FullName should have been called exactly 5 times.");
    Assert.NotNull(actualResult.BlogList, "BlogList should not be null");
    Assert.NotNull(actualResult.BlogList.BlogPosts, "BlogPosts should not be null");
    Assert.True(actualResult.Success, "Success should be true");
    Assert.IsNull(actualResult.Message, "Message should be null");
    Assert.AreEqual(5, actualResult.BlogList.BlogPosts.Length, "5 blog posts are expected");

    Assert.AreEqual(new DateTime(2014, 1, 12, 18, 52, 32), actualResult.BlogList.BlogPosts[0].FileDate, "file3 should be pos 1");
    Assert.AreEqual(new DateTime(2013, 12, 28, 4, 31, 41), actualResult.BlogList.BlogPosts[1].FileDate, "file1 should be pos 2");
    Assert.AreEqual(new DateTime(2013, 10, 9, 1, 54, 43), actualResult.BlogList.BlogPosts[2].FileDate, "file2 should be pos 3");
    Assert.AreEqual(new DateTime(2012, 12, 20, 6, 18, 23), actualResult.BlogList.BlogPosts[4].FileDate, "file4 should be pos 5");
    Assert.AreEqual(new DateTime(2013, 6, 4, 12, 28, 56), actualResult.BlogList.BlogPosts[3].FileDate, "file5 should be pos 4");
}

下面是实现代码:

public GetBlogPostsResponse GetBlogPosts(GetBlogPostsRequest req)
{
    var files = _fs.Directory.GetFiles(contentPath, string.Concat("*", blogFilesExtension));
    if (files == null || files.Length <= 0) {
        return new GetBlogPostsResponse {
            Success = false,
            Message = "No blog posts have been made"
        };
    }

    // Deserialize / map each message
    var orderedFiles = files
        .Select(p => {
            var post = _serializer.Deserialize<BlogPostModel>(_fs.File.ReadAllText(p));
            post.FileDate = GetFileNameDate(p);
            post.FilePath = p;
            return post;
        })
        .OrderByDescending(x => x.FileDate);

    var posts = orderedFiles.ToArray();
    var response = new GetBlogPostsResponse {
        Success = true,
        BlogList = new BlogListModel {
            // BUG: enumerating here is returning a copy of the last element of the array for all elements
            BlogPosts = posts
        }
    };

    return response;
}

我是否遗漏了一些明显的东西?我检查了所有的模拟设置是否正确,上面的调试似乎表明 ToArray() 方法正在玩有趣的乞丐......但这不可能,对吧? :|

【问题讨论】:

    标签: c# arrays linq nunit moq


    【解决方案1】:

    典型 - 排序!如果有的话,在 SO 上发帖让我休息一下,稍后再回来。

    这很有意义,现在我已经弄清楚了……我修改了以下测试行:

    mockSerializer
        .Setup(x => x.Deserialize<BlogPostModel>(It.IsAny<string>()))
        .Returns(new BlogPostModel());
    

    致以下:

    mockSerializer
        .Setup(x => x.Deserialize<BlogPostModel>(It.IsAny<string>()))
        .Returns(() => new BlogPostModel());
    

    必须通过普通的 lambda 覆盖返回 new BlogPostModel()。如果您没有明确告诉它每次都返回一个唯一值,那么看起来 Moq 将重新使用从 Setup 返回的值 - 这解释了为什么它总是返回最后一个元素 - 因为枚举的每个元素只是一个参考到从 Moq 返回的对象。

    呼!

    【讨论】:

    • 正确。在原始版本中,使用.Returns(new BlogPostModel())new 对象创建表达式在调用Returns 及其值(这是对新创建的实例的引用)之前进行评估是传递给Returns 的内容。所以Returns 方法没有得到任何表达式或任何东西,只是一个对特定具体实例的引用,所以除了每次都返回那个引用之外它不能做任何事情。
    猜你喜欢
    • 2018-05-06
    • 2017-04-24
    • 2017-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-14
    相关资源
    最近更新 更多