延迟执行与 ToList 给出不同的结果

发布于 2024-11-29 07:19:31 字数 3791 浏览 1 评论 0原文

我正在对我编写的用于搜索可查询项目的函数进行单元测试。我只是断言我得到了 1 件物品,如果该方法有效的话我应该得到它。但我拿回了 0 件物品。在我的方法中,我使用延迟执行,并在返回之前使用 ToList 。但是,如果我将方法更改为直接使用列表并重复调用 ToList,我会得到正确的结果。

我是否正确地说,假设延迟执行产生与立即执行相同的结果是不安全的?

这是一个小应用程序,用于演示它返回 0 个项目

   class Program
    {

        static void Main(string[] args)
        {
            Dictionary<string, string> values = new Dictionary<string, string>()
            {
                 {
                     "Prop1",
                     "*Value*"
                 },
                 {
                     "Prop2",
                     "2*"
                 }
            };
            List<InputItem> items =new List<InputItem>()
            {
                new InputItem()
            };
            Console.WriteLine(Helper.SearchInputItems(items.AsQueryable(), values).Count);
            Console.ReadLine();
        }
    }

    public class InputItem
    {
        public Dictionary<string, string> MappedValues = new Dictionary<string, string>()
        {
            {
                     "Prop1",
                     "This is a value that should be found"
                 },
                 {
                     "Prop2",
                     "2 everything that begins with 2 should be found"
                 }
        };
    }
    public static class Helper
    {
        delegate bool Searcher(string input, string searchString);
        /// <summary>
        /// Searches the added input items.
        /// </summary>
        /// <param name="values">A dictionary of field names and the search pattern for that field.</param>
        /// <returns>List of found InputItems.</returns>
        public static List<InputItem> SearchInputItems(IQueryable<InputItem> inputItems, Dictionary<string, string> values)
        {
            foreach (var value in values)
            {
                string searchString = value.Value;
                Searcher searcher;
                if (searchString.StartsWith("*") && searchString.EndsWith("*"))
                {
                    searcher = new Searcher(StringHelpers.Contains);
                    searchString = searchString.Substring(1);
                    searchString = searchString.Remove(searchString.Length - 1);
                }
                else if (searchString.EndsWith("*"))
                {
                    searcher = new Searcher(StringHelpers.StartsWith);
                    searchString = searchString.Remove(searchString.Length - 1);
                }
                else
                {
                    searcher = new Searcher(StringHelpers.Exact);
                }
                inputItems = inputItems.Where(c =>
                    c.MappedValues.Any(x => x.Key == value.Key) &&
                    searcher(c.MappedValues.First(x => x.Key == value.Key).Value, searchString)
                    );

            }
            return inputItems.ToList();
        }
    }
    public static class StringHelpers
    {
        public static bool Contains(string input, string searchString)
        {
            return input.ToUpperInvariant().Contains(searchString.ToUpperInvariant());
        }
        public static bool StartsWith(string input, string searchString)
        {
            return input.ToUpperInvariant().StartsWith(searchString.ToUpperInvariant());
        }
        public static bool Exact(string input, string searchString)
        {
            return input.ToUpperInvariant() == searchString.ToUpperInvariant();
        }
    }

如果我设置断点,我实际上可以看到它检查 2 是否应该找到以 2 开头的所有内容 包含 Value ,但它不会并返回 false。所以看起来 where 子句中的 FirstOrDefault 选择了错误的项目

I'm in the middle of unit testing a function I wrote to search a queryable of items. I just assert that I get 1 item back, which I should get if the method works. But I get 0 items back. In my method I use deferred execution and just use ToList before I return it. But if I instead change the method to work directly with a list and repeatedly call ToList, I get the correct results.

Am I correct to say that it is not safe to assume that deferred execution produces the same results as Immediate execution?

This is a small app to demonstrate that it returns 0 items

   class Program
    {

        static void Main(string[] args)
        {
            Dictionary<string, string> values = new Dictionary<string, string>()
            {
                 {
                     "Prop1",
                     "*Value*"
                 },
                 {
                     "Prop2",
                     "2*"
                 }
            };
            List<InputItem> items =new List<InputItem>()
            {
                new InputItem()
            };
            Console.WriteLine(Helper.SearchInputItems(items.AsQueryable(), values).Count);
            Console.ReadLine();
        }
    }

    public class InputItem
    {
        public Dictionary<string, string> MappedValues = new Dictionary<string, string>()
        {
            {
                     "Prop1",
                     "This is a value that should be found"
                 },
                 {
                     "Prop2",
                     "2 everything that begins with 2 should be found"
                 }
        };
    }
    public static class Helper
    {
        delegate bool Searcher(string input, string searchString);
        /// <summary>
        /// Searches the added input items.
        /// </summary>
        /// <param name="values">A dictionary of field names and the search pattern for that field.</param>
        /// <returns>List of found InputItems.</returns>
        public static List<InputItem> SearchInputItems(IQueryable<InputItem> inputItems, Dictionary<string, string> values)
        {
            foreach (var value in values)
            {
                string searchString = value.Value;
                Searcher searcher;
                if (searchString.StartsWith("*") && searchString.EndsWith("*"))
                {
                    searcher = new Searcher(StringHelpers.Contains);
                    searchString = searchString.Substring(1);
                    searchString = searchString.Remove(searchString.Length - 1);
                }
                else if (searchString.EndsWith("*"))
                {
                    searcher = new Searcher(StringHelpers.StartsWith);
                    searchString = searchString.Remove(searchString.Length - 1);
                }
                else
                {
                    searcher = new Searcher(StringHelpers.Exact);
                }
                inputItems = inputItems.Where(c =>
                    c.MappedValues.Any(x => x.Key == value.Key) &&
                    searcher(c.MappedValues.First(x => x.Key == value.Key).Value, searchString)
                    );

            }
            return inputItems.ToList();
        }
    }
    public static class StringHelpers
    {
        public static bool Contains(string input, string searchString)
        {
            return input.ToUpperInvariant().Contains(searchString.ToUpperInvariant());
        }
        public static bool StartsWith(string input, string searchString)
        {
            return input.ToUpperInvariant().StartsWith(searchString.ToUpperInvariant());
        }
        public static bool Exact(string input, string searchString)
        {
            return input.ToUpperInvariant() == searchString.ToUpperInvariant();
        }
    }

If I set a breakpoint I can actually see that it checks if 2 everything that begins with 2 should be found contains Value, which it doesn't and returns false. So it seems like the FirstOrDefault in the where clause selects the wrong item

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

墨离汐 2024-12-06 07:19:31

好的,我知道了。这是 捕获循环变量

这里:

        foreach (var value in values)
        {
            ...
            inputItems = inputItems.Where(c =>
                c.MappedValues.Any(x => x.Key == value.Key) &&
                searcher(c.MappedValues.First(x => x.Key == value.Key).Value, 
                                              searchString)
                );

        }

您在 lambda 表达式中使用 value,这意味着执行时它将使用“value 的当前值”...并且该值会随着循环迭代。

只需使用:

foreach (var valueIterationVariable in values)
{
    var value = valueIterationVariable;
    // code as before
}

我相信就可以了。 (顺便说一句,我会质疑您对名称“value”的使用,但这是另一回事。)

我还没有深入研究为什么它与 IEnumerable 一起使用,但不适用于 IQueryable,但我怀疑额外的延迟是罪魁祸首。

Okay, I've got it. It's the old problem of capturing the loop variable.

Here:

        foreach (var value in values)
        {
            ...
            inputItems = inputItems.Where(c =>
                c.MappedValues.Any(x => x.Key == value.Key) &&
                searcher(c.MappedValues.First(x => x.Key == value.Key).Value, 
                                              searchString)
                );

        }

you're using value within a lambda expression, which means it will use "the current value of value" when that executes... and that value changes as the loop iterates.

Simply use:

foreach (var valueIterationVariable in values)
{
    var value = valueIterationVariable;
    // code as before
}

and I believe it will be okay. (I would question your use of the name "value", by the way, but that's a different matter.)

I haven't looked deeply into why it worked with IEnumerable<T> but not IQueryable<T>, but I suspect the extra deferral was to blame.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文