【问题标题】:How to make this custom worksheet initialization faster?如何使这个自定义工作表初始化更快?
【发布时间】:2011-03-03 23:43:06
【问题描述】:

总结

这个问题在某种程度上是这个问题的后续:
How to implement column self-naming from its index?

在测试了上面链接的问题答案中提供的代码后,我终于遇到了一个严重的性能问题。

性能问题

性能问题发生在 Sheet 初始化时,即当我初始化 Sheet 的单元格时。

    ''' <summary>
    ''' Initialize an instance of the Company.Project.Sheet class.
    ''' </summary>
    ''' <param name="nativeSheet">The native worksheet from which to initialize.</param>
    Friend Sub New(ByVal nativeSheet As Microsoft.Office.Interop.Excel.Worksheet)
        _nativeSheet = nativeSheet
        Dim cells As IDictionary(Of String, ICell) = New Dictionary(Of String, ICell)()

        'These iterations hurt the performance of the API...'
        For rowIndex As Integer = 1 To _nativeSheet.Rows.Count Step 1
            For colIndex As Integer = 1 To _nativeSheet.Columns.Count Step 1
                Dim c As ICell = New Cell(_nativeSheet.Cells(rowIndex, colIndex))
                cellules.Add(c.Name, c)
            Next
        Next

        _cellules = New ReadOnlyDictionary(Of String, ICell)(cells)
    End Sub
  • ReadOnlyDictionary(Of TKey, TValue) :
    A custom read-only dictionary that simply wraps a IDictionary(Of TKey, TValue) to prevent modifications.

讨论

我之所以这样工作,是因为底层电子表格工作表中的每个单元格都是从工作表的初始化开始到最后进行初始化的,也就是说,当工作表被处置或完成时。因此,我希望以同样的方式初始化工作表的单元格,但我也希望保持在命名(“A1”)单元格上使用索引单元格的性能提升,同时保持 API 用户的易用性引用具有其名称的单元格,这就是我打算使用字典的方式,因此当我引用单元格“A1”时,我将这个键访问到我的字典中并相应地寻址单元格 (1, 1)。

  • 除此之外,我还知道一种使用 Worksheet.UsedRange 属性从工作表中读取数据的更快方法,该属性将所有使用过的单元格返回到 2D 矩阵中。

    如果对于我可以用来初始化我的 Cell 类的多个实例的单元集有任何相同或大致相同,那将是非常棒的,而且性能很好!

  • 我还想过在内存中初始化一个 100 x 100 的矩阵单元,同时将它们映射到我的字典,因为很少会使用整个工作表的单元。因此,我仍在考虑一种必须访问尚未初始化的单元格的方法,比如 Cells(120, 120)。理想情况下,我认为,程序必须初始化最大初始初始化 Cell(100, 100) 到 Cell (120, 120) 之间的所有单元格。我在这里够清楚吗?随时要求澄清! =)

  • 另一种选择可能是我只将单元格的名称初始化到字典中并将行和列索引保留在内存中,而不是使用其 nativeCell 初始化 Cell 实例,例如 Range。这是我的 Cell 类的代码来说明我的意思。

    ''' ''' 表示工作表中的一个单元格。 ''' ''' 朋友类细胞 实现 ICell

    Private _nativeCell As Microsoft.Office.Interop.Excel.Range
    Private _name As String
    
    ''' <summary>
    ''' Initializes a new instance of the Company.Project.Cell class.
    ''' </summary>
    ''' <param name="nativeCell">The Microsoft.Office.Interop.Excel.Range to wrap.</param>
    Friend Sub New(ByVal nativeCell As Microsoft.Office.Interop.Excel.Range)
        _nativeCell = nativeCell
    End Sub
    
    Public ReadOnly Property NativeCell() As Microsoft.Office.Interop.Excel.Range Implements ICellule.NativeCell
        Get
            Return _nativeCell 
        End Get
    End Property
    
    Public ReadOnly Property Column() As Integer Implements ICell.Column
        Get
            Return _nativeCell.Column
        End Get
    End Property
    
    Public ReadOnly Property Row() As Integer Implements ICell.Row
        Get
            Return _nativeCell.Row
        End Get
    End Property
    
    Public ReadOnly Property Name() As String Implements ICellule.Name
        Get
            If (String.IsNullOrEmpty(_name) OrElse _name.Trim().Length = 0) Then _
                _name = GetColumnName()
    
            Return _nom
        End Get
    End Property
    
    Public Property Value() As Object Implements ICellule.Value
        Get
            Return _nativeCell.Value2
        End Get
        Set(ByVal value As Object)
            _nativeCell.Value2 = value
        End Set
    End Property
    
    Public ReadOnly Property FormattedValue() As String Implements ICellule.FormattedValue
        Get
            Return _nativeCell.Text
        End Get
    End Property
    
    Public ReadOnly Property NumericValue() As Double? Implements ICellule.NumericValue
        Get
            Return Value
        End Get
    End Property
    

问题

  1. 我还有哪些其他选择?

  2. 还有其他方法可以通过吗?

  3. 有没有一种方法可以使实际方法在性能问题上可行?

供您参考,此问题在测试时超时,因此测试从未在实际上需要几个世纪的可接受时间范围内结束......

欢迎任何想法!我对其他解决方案或方法持开放态度,这些解决方案或方法将帮助我在解决此性能问题的同时实现此目标。

谢谢大家! =)

编辑#1

感谢Maxim Gueivandov,他的解决方案解决了我在这个问题中解决的问题。

除此之外,此解决方案还引发了另一个问题:SystemOutOfMemoryException,这将在另一个问题中解决。

向马克西姆·盖万多夫致以最诚挚的谢意。

【问题讨论】:

    标签: .net performance excel office-interop


    【解决方案1】:

    您可以尝试在一个跃点中获取已使用范围内的所有单元格,从而避免在每次迭代中调用Cells(rowIndex, colIndex)(我猜Cells 隐藏了一个互操作调用,这可能会对性能产生影响)。

    Dim usedRange As Range = nativeSheet.UsedRange
    Dim cells(,) As Object = DirectCast(usedRange.get_Value( _
        XlRangeValueDataType.xlRangeValueDefault), Object(,))
    [... do your row/col iterations ...]
    

    您会在以下文章中找到一些我基于这些假设的性能提示:C# Excel Interop Use。最值得注意的是,检查基准部分:

    === C# 中的 Excel 互操作基准 ===

    细胞[]:30.0 秒

    get_Range(), Cells[]: 15.0 秒

    UsedRange,get_Value():1.5 秒 [最快]

    【讨论】:

    • +1 我绝对同意您和您在此提出的基准。你说得对,usedRange 是最快的方法。此外,我希望实例化工作表的所有单元格,尽管它们没有被使用。例如,考虑一个新的空白工作表。尽管未使用单元格,但它们已被实例化。以 Cells(200, 200) 为例,我不记得见过包含那么多数据的 Excel 文件。虽然没有使用这个单元格,但它的 Range 存在于内存中。这也是我所希望的。那么,有没有类似于UsedRange属性的方法来获取所有单元格呢?
    • @Will:nativeSheet.Cells怎么样?
    • Worksheet.Cells 属性返回一个Range 对象实例,因此它不能像Worksheet.UsedRange 那样被转换为Object(,) 数组。除此之外,我想知道它作为一种方法是否可行。我的架构理念是为用户提供 Excel 互操作 API 提供的所有灵活性,同时降低其复杂性,即从底层电子表格 API 中抽象出来。例如,我不会实现 Range 类中包含的所有属性,同时对最常用的属性(如 Value、Column、Row、Name)进行编码。
    • (continue...) 这就是在我的 ICell 接口中公开 NativeCell 属性的目的。因此,当一个人希望扩展他对库的使用时,他将能够做到,否则,通过我的 API 提出的属性将使底层电子表格应用程序的工作变得更容易。我试过使用Worksheet.Cells 属性并遍历返回的范围,但这和第一手的一样慢。我真的开始考虑将 Sheet.Cells 属性实现为 Object(NativeSheet.Rows, NativeSheet.Columns) 数组(待续......)
    • 并使用批量写入功能将信息数据输出到基础电子表格中,同时在从工作簿读取时使用UsedRange 属性。你怎么看呢?感谢您的时间和提供的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-12
    • 1970-01-01
    • 2014-12-29
    • 2019-11-13
    • 1970-01-01
    • 1970-01-01
    • 2019-11-28
    相关资源
    最近更新 更多