2011-03-03 8 views
7

Resumen¿Qué arquitectura usar para abordar esta excepción SystemOutOfMemoryException mientras me permite crear instancias de las celdas de una hoja?

Esta pregunta es el seguimiento de un deseo de diseñar una API simple hoja de cálculo mientras se mantiene fácil de usar para aquellos que conocen bien Excel.

Para resumir, esta pregunta está relacionada con estos dos debajo:
1. How to implement column self-naming from its index?;
2. How to make this custom worksheet initialization faster?.

Objetivo

Para proporcionar una API de Excel simplificado utilizado como envoltorio sobre los componentes nevralgic como el Application, la Workbook, la Worksheet y los Range clases/interfaces de la vez que expone sólo los más propiedades de objetos comúnmente utilizados para cada uno de estos.

Ejemplo de uso

Este ejemplo de uso se inspira de las pruebas unitarias que me permitieron llevar esta solución hasta donde está ahora.

Dim file as String = "C:\Temp\WriteTest.xls" 

Using mgr As ISpreadsheetManager = New SpreadsheetManager() 
    Dim wb as IWorkbook = mgr.CreateWorkbook() 
    wb.Sheets("Sheet1").Cells("A1").Value = 3.1415926 
    wb.SaveAs(file) 
End Using 

Y ahora lo abrimos:

Dim file as String = "C:\Temp\WriteTest.xls" 

Using mgr As ISpreadsheetManager = New SpreadsheetManager() 
    Dim wb as IWorkbook = mgr.OpenWorkbook(file) 
    // Working with workbook here... 
End Using 

Discusión

Mientras crear instancias de un libro de Excel:

  1. Una instancia de una W orksheet se inicializa automáticamente en el libro de trabajo. Colección de hojas;
  2. Al inicializarse, una Hoja de trabajo inicializa sus celdas a través del objeto Range que puede representar una o varias celdas.

Estas celdas son inmediatamente accesibles con todas sus propiedades tan pronto como exista la hoja de trabajo.

Mi deseo es reproducir este comportamiento para que

  1. El constructor de la clase libro de trabajo inicializa la propiedad de colección Workbook.Sheets con las hojas nativas;
  2. El constructor de la clase Hoja de trabajo inicializa la propiedad de colección Worksheet.Cells con las celdas nativas.

Mi problema proviene del constructor de la clase Worksheet mientras se inicializa la propiedad de colección Worksheet.Cells ilustrada en # 2.

Pensamientos

A raíz de estas cuestiones antes ligado encontrado con problemas, deseo de averiguar otra arquitectura que me permitiría:

  1. Acceso característica específica de una célula Range cuando sea necesario;
  2. Entregar las propiedades más comúnmente utilizadas a través de mi interfaz ICell;
  3. Tener acceso a todas las celdas Range de una hoja de cálculo desde su inicialización.

Teniendo en cuenta que el acceso a una propiedad Range.Value es la interacción más rápida posible con la instancia subyacente de la aplicación de Excel que utiliza Interop.

Entonces, pensé en inicializar mi ReadonlyOnlyDictionary(Of String, ICell) con el nombre de las celdas sin envolver inmediatamente una instancia de la interfaz Range para que simplemente generase los índices de fila y columna junto con el nombre de la celda para indexar mi diccionario, entonces asignando la propiedad Cell.NativeCell solo cuando uno quiere acceder o formatear un rango específico de celda o celda.

De esta forma, los datos en el diccionario se indexarán con el nombre de las celdas obtenidas de los índices de columna generados en el constructor de clase Worksheet. Entonces, cuando uno podría hacer esto:

Using mgr As ISpreadsheetManager = New SpreadsheetManager() 
    Dim wb As IWorkbook = mgr.CreateWorkbook() 
    wb.Sheet(1).Cells("A1").Value = 3.1415926 // #1: 
End Using 

# 1: Esto me permite utilizar los índices de mi clase Cell para escribir el valor dado a la célula específica, que es más rápido que utilizando su nombre directamente contra el Range.

preguntas y preocupaciones

Además, cuando se trabaja con UsedRange.get_Value() o Cells.get_Value(), esta Object(), las matrices.

1. ¿Así que debería estar contento de trabajar con Object(,) matrices para celdas, sin tener la posibilidad de formatearlo de alguna manera?

2. Como arquitecto estas clases de hoja de cálculo y celulares de modo que tenga el mejor rendimiento ofrecido al trabajar con Object(,) matrices, mientras se mantiene la posibilidad de que una instancia de la célula puede representar o envuelva una sola área de celdas?

Gracias a cualquiera de ustedes que se toma el tiempo para leer mi mensaje y mis más sinceras gracias a los que responden.

+0

¿Por qué "Spreadsheet Manager"? ¿Por qué no solo "Hoja de cálculo"? Si el rol de la clase está tan mal definido que no puede pensar en un nombre para él que no sea "administrar", entonces la clase probablemente esté haciendo demasiado. –

+2

En realidad, ¿qué ocurre con los objetos simples generados por la interoperabilidad COM? –

+0

My 'SpreadsheetManager' es responsable de iniciar y cerrar la instancia subyacente de Excel que lo hace invisible para el usuario. Además, se usa como una fábrica de 'Workbook', como se puede ver cuando se invocan los métodos' CreateWorkbook() 'y' OpenWorkbook() '. –

Respuesta

0

La arquitectura utilizada ha pasado por una clase de objeto que nombré CellCollection.Esto es lo que hace:

Sobre la base de estas hipótesis:

  1. Teniendo en cuenta que una hoja de cálculo de Excel tiene 256 columnas y 65.536 líneas;

  2. Teniendo en cuenta que 16,777,216 (256 * 65536) celdas necesitaban ser instanciadas a la vez;

  3. Dado que el uso más común de una hoja de trabajo toma menos de 1,000 líneas y menos de 100 columnas;

  4. Dado que lo necesitaba para poder referirme a las celdas con sus direcciones ("A1"); y

  5. dado que es punto de referencia que el acceso a todos los valores de una vez y cargarlos en un object[,] en la memoria como siendo la forma más rápida de trabajar con una hoja de cálculo de Excel subyacente, *

He considerado no instanciar ninguna de las celdas, permitiendo que mi propiedad CellCollection dentro de mi interfaz IWorksheet se inicialice y vacíe al momento de la creación de instancias, a excepción de un libro existente. Entonces, cuando abro un libro de trabajo, verifico que NativeSheet.UsedRange está vacío o devuelto nulo (Nothing en Visual Basic), de lo contrario, ya obtuve las "células nativas" usadas en la memoria, de modo que solo queda agregarlas en mi diccionario interno CellCollection mientras indexándolos con su dirección respectiva.

Finalmente, Lazy Initialization ¡Patrón de diseño al rescate! =)

public class Sheet : ISheet { 
    public Worksheet(Microsoft.Office.Interop.Excel.Worksheet nativeSheet) { 
     NativeSheet = nativeSheet; 
     Cells = new CellCollection(this); 
    } 

    public Microsoft.Office.Interop.Excel.Worksheet NativeSheet { get; private set; } 

    public CellCollection Cells { get; private set; } 
} 

public sealed class CellCollection { 
    private IDictionary<string, ICell> _cells; 
    private ReadOnlyDictionary<string, ICell> _readonlyCells; 

    public CellCollection(ISheet sheet) { 
     _cells = new Dictionary<string, ICell>(); 
     _readonlyCells = new ReadonlyDictionary<string, ICell>(_cells); 
     Sheet = sheet; 
    } 

    public readonly ReadOnlyDictionary<string, ICell> Cells(string addresses) { 
     get { 
      if (string.IsNullOrEmpty(addresses) || 0 = address.Trim().Length) 
       throw new ArgumentNullException("addresses"); 

      if (!Regex.IsMatch(addresses, "(([A-Za-z]{1,2,3}[0-9]*)[:,]*)")) 
       throw new FormatException("addresses"); 

      foreach(string address in addresses.Split(",") { 
       Microsoft.Office.Interop.Excel.Range range = Sheet.NativeSheet.Range(address) 

       foreach(Microsoft.Office.Interop.Excel.Range cell in range) { 
        ICell c = null; 
        if (!_cells.TryGetValue(cell.Address(false, false), c)) { 
         c = new Cell(cell); 
         _cells.Add(c.Name, c); 
        } 
       } 
      } 

      return _readonlyCells; 
     } 
    } 

    public readonly ISheet Sheet { get; private set; } 
} 

Obviamente, este es un primer disparo intento, y funciona muy bien hasta ahora, con un rendimiento más que aceptable. Humildemente, siento que podría usar algunas optimizaciones, aunque lo usaré de esta manera por ahora, y lo optimizaré más adelante si es necesario.

Después de haber escrito esta colección, pude lograr el comportamiento esperado. Ahora, intentaré implementar algunas de las interfaces .NET para que sea utilizable contra algunos IEnumerable, IEnumerable<T>, ICollection, ICollection<T>, etc., de modo que se puedan considerar respectivamente como una verdadera colección .NET.

Siéntase libre de comentar y presentar alternativas constructivas y/o cambios a este código para que pueda ser incluso mayor de lo que es actualmente.

Espero que esto sirva para un propósito algún día.

¡Gracias por leer! =)

Cuestiones relacionadas