为什么在IEnumerable上调用ToList(在orderBy之后)时会出现Stackoverflow异常?

时间:2015-10-02 09:41:15

标签: vb.net linq sql-order-by ienumerable

我有一段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可以避免这个问题,但如果有人能够指出真正的原因,我会很感激。

1 个答案:

答案 0 :(得分:1)

显然,您对这些LINQ方法的作用有错误的理解。每当您致电.Where().Except()等时,都会创建一个新的枚举器。此枚举器仅保存其特征(例如Where枚举器保存其谓词)。查询尚未执行。它仅在需要数据时执行,例如当你在枚举器上调用ToList()时。这就是为什么这是抛出异常的地方。

在您的具体示例中,问题是由OrderBy函数引起的。看起来,它创造了无限的枚举。但是,我无法弄清楚原因。必须是一些讨厌的实施细节。

反正。迫使LINQ进入这个问题的方法是错误的。以传统方式实现它更有效,更容易。这使您可以明确指定加速数据结构的使用方式(例如HashSetsDictionaries)以及数据排序的时间和频率。由于其本地范围,所有LINQ枚举器必须在需要时重新创建此结构。