2009-09-30 15 views
9

tengo una matriz en C# que es 1-basado (generado a partir de una llamada a Get_Value para un rango de Excel consigo una matriz 2D por ejemploRebase una matriz de base 1 en C#

object[,] ExcelData = (object[,]) MySheet.UsedRange.get_Value(Excel.XlRangeValueDataType.xlRangeValueDefault); 

esto parece como una matriz, por ejemplo, ExcelData [1..20,1..5]

¿hay alguna manera de decirle al compilador que rebase este modo que no necesito añadir 1 a contadores de bucles todo el tiempo?

List<string> RowHeadings = new List<string>(); 
string [,] Results = new string[MaxRows, 1] 
for (int Row = 0; Row < MaxRows; Row++) { 
    if (ExcelData[Row+1, 1] != null) 
     RowHeadings.Add(ExcelData[Row+1, 1]); 
     ... 
     ... 
     Results[Row, 0] = ExcelData[Row+1, 1]; 
     & other stuff in here that requires a 0-based Row 
} 

Hace que las cosas le Es legible ya que al crear una matriz para escribir la matriz se basará en cero.

+0

Las matrices en C# siempre están basadas en 0, ¿quiere decir que sus datos están basados ​​en 1? – cjk

+6

Las matrices * declaradas * en el código fuente de C# siempre están basadas en 0, pero la CLR admite matrices con límites inferiores arbitrarios. –

+1

Las matrices _created_ in C# bien pueden estar basadas en cero, sin embargo, creo que estoy en lo cierto al afirmar que la matriz aquí está basada en 1; si intenta acceder a ExcelData [0,0], se lanza IncexOutOfRangeException. – GalleySlave

Respuesta

10

¿Por qué no cambiar simplemente su índice?

List<string> RowHeadings = new List<string>(); 
for (int Row = 1; Row <= MaxRows; Row++) { 
    if (ExcelData[Row, 1] != null) 
     RowHeadings.Add(ExcelData[Row, 1]); 
} 

Editar: Aquí es un método de extensión que crearía una nueva matriz, basada en cero desde su original (básicamente sólo crea una nueva matriz que es un elemento más pequeño y copias a la nueva matriz de todo los elementos excepto el primer elemento que actualmente está saltando todos modos):

public static T[] ToZeroBasedArray<T>(this T[] array) 
{ 
    int len = array.Length - 1; 
    T[] newArray = new T[len]; 
    Array.Copy(array, 1, newArray, 0, len); 
    return newArray; 
} 

dicho esto es necesario considerar si la pena (aunque sea leve) de crear una nueva matriz vale la pena mejorar la legibilidad del código. No estoy emitiendo un juicio (muy bien puede valer la pena). Solo me aseguro de no ejecutar este código si perjudica el rendimiento de la aplicación.

+0

No puedo creer que Tree haya publicado la misma respuesta al mismo tiempo :) – Philippe

+0

lo siento, el código que proporcioné es demasiado simple: Row se usa en otro lugar del ciclo for que requiere un conteo basado en cero. – GalleySlave

1

¿Cambiar el contador de bucle es demasiado difícil para usted?

for (int Row = 1; Row <= MaxRows; Row++) 

Si el rango del contador es correcto, usted no tiene que añadir 1 a cualquier cosa dentro del bucle de manera que no pierda legibilidad. Mantenlo simple.

+10

sin necesidad de ser demasiado paternalista - ver los cambios a la pregunta original (otros dijeron lo mismo un poco más constructivamente) ... – GalleySlave

3

Por qué no usar:

for (int Row = 1; Row <= MaxRows; Row++) { 

o hay algo que me falta?

EDITAR: como resulta que falta algo, utilizaría otro contador (comenzando en 0) para ese propósito, y usaría un índice de fila de 1 para la matriz. No es una buena práctica usar el índice para otro uso que no sea el índice en la matriz objetivo.

+0

algo que falta :) ver comentario arriba ... – GalleySlave

+0

Jason publicó una buena respuesta que ilustra mi edición. – Philippe

6

Cree un contenedor para la matriz ExcelData con un indexador this[,] y realice la lógica de rebase allí. Algo así como:

class ExcelDataWrapper 
{ 
    private object[,] _excelData; 
    public ExcelDataWrapper(object[,] excelData) 
    { 
     _excelData = excelData; 
    } 
    public object this[int x, int y] 
    { 
     return _excelData[x+1, y+1]; 
    } 
} 
+0

Me gusta esta solución porque puede hacerlo dinámico. Agregué un parámetro de 'inicio de índice' al constructor para que esta clase se pueda usar por igual en matrices basadas en 1 y 0. También agregué las propiedades 'RowCount' y' ColumnCount' en lugar de tener que especificar 'array.GetLength (x)' – ChandlerPelhams

6

Ya que se necesita permanecer tal como está (en base a sus comentarios), sólo podría introducir otra variable de bucle:

List<string> RowHeadings = new List<string>(); 
string [,] Results = new string[MaxRows, 1] 
for (int Row = 0, SrcRow = 1; SrcRow <= MaxRows; Row++, SrcRow++) { 
    if (ExcelData[SrcRow, 1] != null) 
     RowHeadings.Add(ExcelData[SrcRow, 1]); 
     ... 
     ... 
     Results[Row, 0] = ExcelData[SrcRow, 1]; 
} 
0

Estoy de acuerdo que el trabajo con la base-1 matrices de .NET puede ser una molestia. También es potencialmente propenso a errores, ya que tiene que hacer un cambio mental cada vez que lo usa, así como recordar correctamente qué situaciones serán la base 1 y cuál será la base 0.

El enfoque más eficiente es simplemente haga estos cambios mentales e indexe adecuadamente, usando base-1 o base-0 según sea necesario.

Personalmente, prefiero convertir las matrices bidimensionales base-1 en matrices bidimensionales base-0. Desafortunadamente, esto requiere el rendimiento de copiar sobre la matriz a una nueva matriz, ya que no hay forma de volver a basar una matriz en su lugar.

He aquí un método de extensión que pueden hacer esto para las matrices 2D devueltos por Excel:

public static TResult[,] CloneBase0<TSource, TResult>(
    this TSource[,] sourceArray) 
{ 
    If (sourceArray == null) 
    { 
     throw new ArgumentNullException(
      "The 'sourceArray' is null, which is invalid."); 
    } 

    int numRows = sourceArray.GetLength(0); 
    int numColumns = sourceArray.GetLength(1); 
    TResult[,] resultArray = new TResult[numRows, numColumns]; 

    int lb1 = sourceArray.GetLowerBound(0); 
    int lb2 = sourceArray.GetLowerBound(1); 

    for (int r = 0; r < numRows; r++) 
    { 
     for (int c = 0; c < numColumns; c++) 
     { 
      resultArray[r, c] = sourceArray[lb1 + r, lb2 + c]; 
     } 
    } 

    return resultArray; 
} 

y luego se puede utilizar de esta manera:

object[,] array2DBase1 = (object[,]) MySheet.UsedRange.get_Value(Type.Missing); 

object[,] array2DBase0 = array2DBase1.CloneBase0(); 

for (int row = 0; row < array2DBase0.GetLength(0); row++) 
{ 
    for (int column = 0; column < array2DBase0.GetLength(1); column++) 
    { 
     // Your code goes here... 
    } 
} 

Para las matrices de forma masiva de tamaño, es posible que No quiero hacer esto, pero creo que, en general, realmente limpia tu código (y tu mentalidad) para hacer esta conversión, y luego siempre funciona en base-0.

Espero que esto ayude ...

Mike

+0

gracias - una optimización, una vez validadas las longitudes, es posible que desee utilizar System.Array.Copy (de, a, longitud) que (¿supuestamente?) copia la matriz 2D más eficientemente. – GalleySlave

+0

Es una buena idea, excepto que no se puede. El método 'Array.Copy' solo funciona con matrices de 1 dimensión, mientras que la matriz devuelta por Excel.Range.get_Value es una matriz bidimensional. Entonces, el método de extensión debe devolver TResult [,] basado en TSource [,] y no puede usar 'Array.Copy'. ¡Sin elección! –

+0

Por cierto, utilizo este enfoque para todo mi código cuando trato con matrices de base 1 devueltas por Excel. Supongo que estoy reduciendo la velocidad de todo mi código, pero no tanto como para haberlo notado, y la capacidad de tratar siempre en arreglos de base-0 realmente me ayuda a mantener la cordura. :) –

-1

Se puede usar un tercio partido Excel componente compatible, como que ha .NET API amigables - incluyendo la indexación basada 0 para las API como Irange [int rowIndex , int colIndex].

Dichos componentes también serán mucho más rápidos que la API de Excel en casi todos los casos.

responsabilidad: Soy dueño de SpreadsheetGear LLC

+0

Gracias, aunque parece caro para todo lo que necesito actualmente (un conjunto de envoltorios) - no hay mención de ordenar que encontré - ¿Todavía tendría que manejar eso o SpreadsheetGear lo maneja todo? – GalleySlave

+0

SpreadsheetGear no es solo un contenedor para Excel, sino un ensamblado .NET seguro escrito en C#. No utiliza COM Interop para nada. No se basa en Excel. Es un componente de hoja de cálculo compatible con Excel con su propio motor de cálculo, edición, formateo, representación, etc. En muchos casos, los clientes nos dicen que las aplicaciones se aceleran drásticamente cuando pasan de COM Interop/Excel Automation a SpreadsheetGear. También está libre de preocuparse por la versión de Excel que tengan los usuarios. –

0

Para 1 matrices basadas y las operaciones de rango de Excel, así como funciones UDF (SharePoint) que utilice esta función de utilidad

public static object[,] ToObjectArray(this Object Range) 
    { 
     Type type = Range.GetType(); 
     if (type.IsArray && type.Name == "Object[,]") 
     { 
      var sourceArray = Range as Object[,];    

      int lb1 = sourceArray.GetLowerBound(0); 
      int lb2 = sourceArray.GetLowerBound(1); 
      if (lb1 == 0 && lb2 == 0) 
      { 
       return sourceArray; 
      } 
      else 
      { 
       int numRows = sourceArray.GetLength(0); 
       int numColumns = sourceArray.GetLength(1); 
       var resultArray = new Object[numRows, numColumns]; 
       for (int r = 0; r < numRows; r++) 
       { 
        for (int c = 0; c < numColumns; c++) 
        { 
         resultArray[r, c] = sourceArray[lb1 + r, lb2 + c]; 
        } 
       } 

       return resultArray; 
      } 

     } 
     else if (type.IsCOMObject) 
     { 
      // Get the Value2 property from the object. 
      Object value = type.InvokeMember("Value2", 

        System.Reflection.BindingFlags.Instance | 

        System.Reflection.BindingFlags.Public | 

        System.Reflection.BindingFlags.GetProperty, 

        null, 

        Range, 

        null); 
      if (value == null) 
       value = string.Empty; 
      if (value is string) 
       return new object[,] { { value } }; 
      else if (value is double) 
       return new object[,] { { value } }; 
      else 
      { 
       object[,] range = (object[,])value; 

       int rows = range.GetLength(0); 

       int columns = range.GetLength(1); 

       object[,] param = new object[rows, columns]; 

       Array.Copy(range, param, rows * columns); 
       return param; 
      } 
     } 

     else 
      throw new ArgumentException("Not A Excel Range Com Object"); 

    } 

Uso

public object[,] RemoveZeros(object range) 
    { 
     return this.RemoveZeros(range.ToObjectArray()); 
    } 
    [ComVisible(false)] 
    [UdfMethod(IsVolatile = false)] 
    public object[,] RemoveZeros(Object[,] range) 
    {...} 

La primera función es com visible y aceptará un rango de Excel o una llamada encadenada desde otra función (la llamada encadenada devolverá una matriz de objetos basada en 1), la segunda llamada es UDF habilitado para Servicios de Excel en SharePoint. Toda la lógica está en la segunda función. En este ejemplo, simplemente estamos reformateando un rango para reemplazar cero con string.empty.