2009-09-30 8 views
9

Mam tablicy w C#, który jest 1 na bazie (wygenerowany z wywołania get_Value dla programu Excel Zakres dostaję 2D tablicę na przykład pojawia się tenrebase tablicę 1-siedzibą w C#

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

jako tablica na przykład [1..20,1..5]

ExcelData

jest jakiś sposób, aby poinformować kompilator rebase to tak, że nie trzeba dodać 1 do liczników pętli przez cały czas?

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 
} 

To sprawia, że ​​rzeczy leżą ss czytelny od kiedy tworzenie tablicy do zapisu tablicy będzie oparte na zero.

+0

Tablice w C# są zawsze oparte na 0, czy masz na myśli twoje dane oparte na 1? – cjk

+6

Tablice * zadeklarowane * w kodzie źródłowym C# są zawsze oparte na 0, ale CLR obsługuje tablice z dowolnie niższymi granicami. –

+1

Tablice _created_w C# mogą być oparte na zerach, jednak uważam, że mam rację stwierdzając, że tablica jest tutaj 1 - jeśli spróbujesz uzyskać dostęp do ExcelData [0,0], zostanie zgłoszony wyjątek IncexOutOfRangeException. – GalleySlave

Odpowiedz

10

Dlaczego po prostu nie zmienić indeksu?

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

Edit: Oto metoda rozszerzenie, które może stworzyć nową, tablicę od zera ze swojego pierwotnego (w zasadzie to tylko tworzy nową tablicę, która jest jednym z elementów mniejszych i kopiuje do tej nowej tablicy wszystko elementy ale pierwszy element, który aktualnie pomijając zresztą):

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; 
} 

takiej sytuacji trzeba rozważyć, czy kara (choć niewielki) tworzenia nowej tablicy jest warte poprawy czytelności kodu. Nie wydaję osądu (bardzo dobrze może być warto) Po prostu upewniam się, że nie uruchomisz tego kodu, jeśli będzie to szkodliwe dla wydajności twojej aplikacji.

+0

Nie mogę uwierzyć, że drzewo z nas wysłało tę samą odpowiedź w tym samym czasie :) – Philippe

+0

Przepraszam - kod, który dostarczyłem jest zbyt prosty - Wiersz jest używany gdzie indziej w pętli for wymagającej zliczania zera. – GalleySlave

1

Czy zbyt często zmieniasz licznik pętli?

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

Jeśli zakres licznika jest prawidłowy, nie trzeba dodawać 1 do niczego w pętli, aby nie stracić czytelności. Nie komplikuj.

+10

nie trzeba być zbyt protekcjonalnym - zobacz zmiany do pierwotnego pytania (inni powiedzieli to samo nieco bardziej konstruktywnie) ... – GalleySlave

3

Dlaczego nie używać:

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

Czy jest tam coś mi brakuje?

EDYCJA: ponieważ okazuje się, że czegoś brakuje, użyłbym w tym celu innego licznika (zaczynając od 0) i użyłbym indeksu wiersza 1 dla tablicy. Nie jest dobrą praktyką korzystanie z indeksu do innego celu niż indeks w tablicy docelowej.

+0

coś brakuje :) patrz komentarz powyżej ... – GalleySlave

+0

Jason opublikował ładną odpowiedź, która ilustruje moją edycję. – Philippe

6

Utwórz opakowanie dla tablicy ExcelData za pomocą indeksera this[,] i przeprowadź w nim logikę ponownego wprowadzania. Coś jak:

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

Podoba mi się to rozwiązanie, ponieważ możesz sprawić, że będzie dynamiczny. Dodałem parametr 'start index' do konstruktora, więc ta klasa może być używana zarówno dla tablic opartych na 1 i 0. Dodałem również właściwości 'RowCount' &' ColumnCount' zamiast określać 'tablica.GetLength (x)' – ChandlerPelhams

6

ponieważ trzeba pozostać jak jest (w oparciu o komentarze), można po prostu wprowadzić innej zmiennej pętli:

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

Zgadzam się, że praca z bazą-1 macierzy z .NET może być uciążliwe. Jest również potencjalnie podatny na błędy, ponieważ musisz mentalnie dokonywać zmian za każdym razem, gdy go używasz, a także prawidłowo pamiętać, które sytuacje będą podstawą 1, i która będzie podstawowa 0.

Najbardziej wydajnym podejściem jest po prostu wykonaj te mentalne przesunięcia i indeksuj odpowiednio, używając base-1 lub base-0, jeśli jest to wymagane.

Ja osobiście wolę konwertować dwuwymiarowe tablice base-1 na dwuwymiarowe tablice base-0. To niestety wymaga uderzenia wydajności kopiowania tablicy do nowej tablicy, ponieważ nie ma sposobu na ponowne zbudowanie tablicy w miejscu.

Oto metoda rozszerzenie, które może zrobić to za 2D tablic zwróconych przez 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; 
} 

I wtedy można go używać tak:

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... 
    } 
} 

Dla masowo wielkości tablic, to polubisz Nie chcę tego robić, ale ogólnie uważam, że to naprawdę oczyszcza twój kod (i nastawiony na umysł), aby dokonać tej konwersji, a następnie zawsze działa w base-0.

Nadzieja to pomaga ...

Mike

+0

dzięki - optymalizacja, po sprawdzeniu poprawności długości, może chcesz użyć System.Array.Copy (od, do, długość), która (prawdopodobnie?) bardziej wydajnie kopiuje macierz 2D. – GalleySlave

+0

To niezły pomysł, tyle że nie możesz. Metoda "Array.Copy" działa tylko z jednowymiarowymi tablicami, podczas gdy tablica zwrócona przez Excel.Range.get_Value jest tablicą dwuwymiarową. Tak więc metoda rozszerzenia musi zwrócić TResult [,] na podstawie TSource [,] i nie może użyć "Array.Copy". Bez wyboru! –

+0

Nawiasem mówiąc, używam tego podejścia do całego mojego kodu, gdy mam do czynienia z tablicami base-1 zwróconymi przez Excel. Przypuszczam, że spowalniam cały mój kod, ale nie tak bardzo, jak zauważyłem, a umiejętność radzenia sobie z tablicami base-0 naprawdę pomaga utrzymać mnie przy zdrowych zmysłach. :) –

-1

Można używać 3rd party Excel zgodny składnik taki jak który .NET przyjazne API - łącznie 0 indeksacji w oparciu o API, takich jak IRange [int rowIndex , int colIndex].

Takie komponenty będą również w niemal wszystkich przypadkach znacznie szybsze niż interfejs API programu Excel.

Zastrzeżenie: Jestem właścicielem SpreadsheetGear LLC

+0

Dzięki, choć wydaje mi się to drogie dla wszystkiego, czego potrzebuję obecnie (zestaw owijaczy) - nie ma wzmianki o zestawieniu, które znalazłem - czy nadal będę musiał sobie z tym poradzić lub czy SpreadsheetGear poradzi sobie z tym wszystkim? – GalleySlave

+0

SpreadsheetGear to nie tylko opakowanie programu Excel, ale bezpieczne połączenie .NET napisane w języku C#. To nie używa COM Interop do niczego. Nie polega na Excelu. Jest to element arkusza kalkulacyjnego kompatybilny z Excelem z własnym obliczaniem, edytowaniem, formatowaniem, renderowaniem itp. W wielu przypadkach klienci mówią nam, że aplikacje znacznie przyspieszają, gdy przechodzą z COM Interop/Excel Automation do SpreadsheetGear. Jesteś również wolny od obaw o to, którą wersję programu Excel masz dla użytkowników. –

0

Dla 1 tablic opartych i operacji Zakres Excel, jak również funkcji UDF (SharePoint) Używam tę funkcję użytkową

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"); 

    } 

Wykorzystanie

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

Pierwsza funkcja jest widoczna i zaakceptuje zakres excela lub połączenie łańcuchowe z innej funkcji (połączenie łańcuchowe zwróci tablicę obiektów 1), drugie połączenie to UDF włączone dla usług programu Excel w programie SharePoint. Cała logika znajduje się w drugiej funkcji. W tym przykładzie po prostu formatujemy zakres, aby zastąpić zero ciągiem.empty.