我有一段LINQ代码会导致Stack Overflow异常,我无法理解为什么。我将保持代码的原样,因为我无法确定哪个部分是原因。
考虑一下我们有一份预订记录清单:
Class BookRecords
Public START_DATE As String
Public END_DATE As String
Public BOOK_NUM As String
End Class
任务是找出所有记录(1)开始时间在15分钟之内& (2)连续记录在(1)中。
我是这样做的:
Private Function ValidRec(RecordList As List(Of BookRecords)) As List(Of BookRecords)
Dim timeNow = Date.Now
'Valid records by itself
Dim validRecords = RecordList.Where(
Function(r)
Dim startDate As DateTime
'VVVVV stack overflow thrown at this return
Return DateTime.TryParse(r.START_DATE, startDate) AndAlso
((startDate - timeNow).TotalMinutes < 15)
End Function)
Do
'consecutive records
Dim conseRecords = RecordList.Except(validRecords).Where(
Function(r) validRecords.Any(
Function(vr) vr.END_DATE.Equals(r.START_DATE)))
If Not conseRecords.Any() Then Exit Do
validRecords = validRecords.Concat(conseRecords.Except(validRecords))
Loop
validRecords = validRecords.OrderBy(Function(vr) vr.START_DATE) _
.ThenBy(Function(vr) vr.BOOK_NUM)
'stack overflow after the ToList line
Return validRecords.ToList
End Function
代码正常工作,直到最后一行validRecords.ToList之后。然后在Return DateTime.TryParse ...语句中发生堆栈溢出。 RecordList和validRecords的大小很小(测试中都是2),并且没有其他线程修改这些列表/对象。
为什么这会耗尽堆栈?我知道LINQ语句可能结构糟糕并且不必要地创建了大量列表(IEnumerables?),但原因是什么? (那将是真正的堆栈溢出然后,很酷)
我认为将validRecords设为一个列表而不是IEnumerable可以避免这个问题,但如果有人能够指出真正的原因,我会很感激。
答案 0 :(得分:1)
显然,您对这些LINQ方法的作用有错误的理解。每当您致电.Where()
,.Except()
等时,都会创建一个新的枚举器。此枚举器仅保存其特征(例如Where
枚举器保存其谓词)。查询尚未执行。它仅在需要数据时执行,例如当你在枚举器上调用ToList()
时。这就是为什么这是抛出异常的地方。
在您的具体示例中,问题是由OrderBy
函数引起的。看起来,它创造了无限的枚举。但是,我无法弄清楚原因。必须是一些讨厌的实施细节。
反正。迫使LINQ进入这个问题的方法是错误的。以传统方式实现它更有效,更容易。这使您可以明确指定加速数据结构的使用方式(例如HashSets
或Dictionaries
)以及数据排序的时间和频率。由于其本地范围,所有LINQ枚举器必须在需要时重新创建此结构。