延迟执行与 ToList 给出不同的结果
我正在对我编写的用于搜索可查询项目的函数进行单元测试。我只是断言我得到了 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
好的,我知道了。这是 捕获循环变量。
这里:
您在 lambda 表达式中使用
value
,这意味着执行时它将使用“value
的当前值”...并且该值会随着循环迭代。只需使用:
我相信就可以了。 (顺便说一句,我会质疑您对名称“value”的使用,但这是另一回事。)
我还没有深入研究为什么它与
IEnumerable
一起使用,但不适用于IQueryable
,但我怀疑额外的延迟是罪魁祸首。Okay, I've got it. It's the old problem of capturing the loop variable.
Here:
you're using
value
within a lambda expression, which means it will use "the current value ofvalue
" when that executes... and that value changes as the loop iterates.Simply use:
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 notIQueryable<T>
, but I suspect the extra deferral was to blame.