Ant*_*ean 4 c# linq enumeration out
我正在将Excel电子表格转换为"元素"列表(这是一个域名术语).在此转换期间,我需要跳过标题行并抛出无法转换的格式错误的行.
有趣的来了.我需要捕获那些格式错误的记录,以便我可以报告它们.我构建了一个疯狂的LINQ语句(如下).这些扩展方法隐藏了OpenXml库中类型的凌乱LINQ操作.
var elements = sheet
.Rows() <-- BEGIN sheet data transform
.SkipColumnHeaders()
.ToRowLookup()
.ToCellLookup()
.SkipEmptyRows() <-- END sheet data transform
.ToElements(strings) <-- BEGIN domain transform
.RemoveBadRecords(out discard)
.OrderByCompositeKey();
Run Code Online (Sandbox Code Playgroud)
有趣的部分开始于ToElements,我将行查找转换为我的域对象列表(详细信息:它被称为an ElementRow,后来转换为a Element).只使用一个键(Excel行索引)创建错误记录,并且与真实元素相比是唯一可识别的.
public static IEnumerable<ElementRow> ToElements(this IEnumerable<KeyValuePair<UInt32Value, Cell[]>> map)
{
return map.Select(pair =>
{
try
{
return ElementRow.FromCells(pair.Key, pair.Value);
}
catch (Exception)
{
return ElementRow.BadRecord(pair.Key);
}
});
}
Run Code Online (Sandbox Code Playgroud)
然后,我想删除那些不良记录(在过滤之前更容易收集所有这些记录).那个方法就是RemoveBadRecords这样开始......
public static IEnumerable<ElementRow> RemoveBadRecords(this IEnumerable<ElementRow> elements)
{
return elements.Where(el => el.FormatId != 0);
}
Run Code Online (Sandbox Code Playgroud)
但是,我需要报告丢弃的元素!而且我不想通过报告来混淆我的变换扩展方法.所以,我去了out参数(考虑到在匿名块中使用out param 的困难)
public static IEnumerable<ElementRow> RemoveBadRecords(this IEnumerable<ElementRow> elements, out List<ElementRow> discard)
{
var temp = new List<ElementRow>();
var filtered = elements.Where(el =>
{
if (el.FormatId == 0) temp.Add(el);
return el.FormatId != 0;
});
discard = temp;
return filtered;
}
Run Code Online (Sandbox Code Playgroud)
哦,瞧!我以为我是铁杆,并且会一次性工作......
var discard = new List<ElementRow>();
var elements = data
/* snipped long LINQ statement */
.RemoveBadRecords(out discard)
/* snipped long LINQ statement */
discard.ForEach(el => failures.Add(el));
foreach(var el in elements)
{
/* do more work, maybe add more failures */
}
return new Result(elements, failures);
Run Code Online (Sandbox Code Playgroud)
但是,discard当我浏览它时,我的列表中没有任何内容!我逐步完成了代码并意识到我成功创建了一个完全流式LINQ语句.
Where过滤器被分配(但尚未运行)当discard被重复,它不包含元素,因为内容没有迭代结束.
有没有办法使用我构建的东西来解决这个问题?我是否必须在错误记录过滤器之前或期间强制重复数据?我错过了另一种建筑吗?
Jon提到了任务/正在/正在发生.我只是没有等待它.如果我检查discard迭代后的内容elements,实际上是完整的!所以,我实际上没有任务分配问题.除非我接受Jon关于LINQ语句中哪些好/坏的建议.
当语句实际迭代时,Where子句运行并且临时填充,但丢弃从未再次分配!
它不需要再次分配 - discard将填充将在调用代码中分配的现有列表.
但是,我强烈建议不要采用这种方法.out在这里使用参数确实违背了LINQ的精神.(如果你两次迭代你的结果,你最终会得到一个包含所有坏元素两次的列表.等等!)
我建议在删除坏记录之前实现查询- 然后你可以运行单独的查询:
var allElements = sheet
.Rows()
.SkipColumnHeaders()
.ToRowLookup()
.ToCellLookup()
.SkipEmptyRows()
.ToElements(strings)
.ToList();
var goodElements = allElements.Where(el => el.FormatId != 0)
.OrderByCompositeKey();
var badElements = allElements.Where(el => el.FormatId == 0);
Run Code Online (Sandbox Code Playgroud)
通过在物化查询List<>,你只有在以下方面处理每一行一次ToRowLookup,ToCellLookup等它意味着你需要有足够的内存来保存所有的元素在时间,当然.还有其他方法(例如对每个坏元素进行操作,同时对其进行过滤)但它们仍然可能最终变得相当脆弱.
编辑:Servy提到的另一个选项是使用ToLookup,它将实现并一次性分组:
var lookup = sheet
.Rows()
.SkipColumnHeaders()
.ToRowLookup()
.ToCellLookup()
.SkipEmptyRows()
.ToElements(strings)
.OrderByCompositeKey()
.ToLookup(el => el.FormatId == 0);
Run Code Online (Sandbox Code Playgroud)
然后你可以使用:
foreach (var goodElement in lookup[false])
{
...
}
Run Code Online (Sandbox Code Playgroud)
和
foreach (var badElement in lookup[true])
{
...
}
Run Code Online (Sandbox Code Playgroud)
请注意,这会对所有元素执行排序,无论好坏.另一种方法是从原始查询中删除顺序并使用:
foreach (var goodElement in lookup[false].OrderByCompositeKey())
{
...
}
Run Code Online (Sandbox Code Playgroud)
我个人并不喜欢用真/假分组 - 感觉有点滥用通常意味着基于密钥的查找 - 但它肯定会起作用.
| 归档时间: |
|
| 查看次数: |
153 次 |
| 最近记录: |