11

I would like to convert an Excel Range to a C# Array with this code:

System.Array MyRange = (System.Array)range.cells.value;

for (int k = 0; k <= MyRange.Length; k++)
{
    List<service_name> _ml = new List<service_name>();
    for (int j = 1; j < dataitems.Count; j++)
    {
        // enter code here
    }
}

And then iterate over it like in the above loop.

But this code does not work, and throws this Exception:

"Unable to cast object of type 'System.String' to type 'System.Array'."

1
  • 2
    They should be capital like Range.Cells.Value at least. But there is no enough information about your problem. Please clarify it. Commented Mar 31, 2014 at 11:39

6 Answers 6

8

Based on the help provided my Microsoft here, this is how I read and write an array in Excel.

var xlApp = new Microsoft.Office.Interop.Excel.Application();
var wb = xlApp.Workbooks.Open(fn, ReadOnly: false);
xlApp.Visible = true;
var ws = wb.Worksheets[1] as Worksheet;
var r = ws.Range["A2"].Resize[100, 1];
var array = r.Value;
// array is object[1..100,1..1]
for(int i = 1; i <= 100; i++)
{
    var text = array[i, 1] as string;
    Debug.Print(text);
}
// to create an [1..100,1..1] array use
var array2 = Array.CreateInstance(
    typeof(object), 
    new int[] {100, 1}, 
    new int[] {1, 1}) as object[,];

// fill array2
for(int i = 1; i <= 100; i++)
{
    array2[i, 1] = string.Format("Text{0}", i);
}
r.Value2 = array2;

wb.Close(SaveChanges: true);
xlApp.Quit();

Debug Excel

Sign up to request clarification or add additional context in comments.

1 Comment

6

The error message in the original post occurs when the range consists of exactly one cell, because the resulting value's type is variant, and actually can be array, double, string, date, and null.

One solution can be that you check the cell count and act differently in case of exactly one cell.

My solution creates an array of cells. This works even if one or more cells are empty, which could causes a null object. (When all cells of the range are empty, Range.Cells.Value is null.)

System.Array cellArray = range.Cells.Cast<Excel.Range>().ToArray<Excel.Range>();

If you prefer Lists over Arrays (like I do), you can use this code:

List<Excel.Range> listOfCells = range.Cells.Cast<Excel.Range>().ToList<Excel.Range>();

Range can be one- or two-dimensional.

Finally, if you definitely need a string array, here it is:

string[] strArray = range.Cells.Cast<Excel.Range>().Select(Selector).ToArray<string>();

where Selector function looks like this:

public string Selector(Excel.Range cell)
{
    if (cell.Value2 == null)
        return "";
    if (cell.Value2.GetType().ToString() == "System.Double")
        return ((double)cell.Value2).ToString();
    else if (cell.Value2.GetType().ToString() == "System.String")
        return ((string)cell.Value2);
    else if (cell.Value2.GetType().ToString() == "System.Boolean")
        return ((bool)cell.Value2).ToString();
    else
        return "unknown";
}

I included "unknown" for the case I don't remember all data types that Value2 can return. Please improve this function if you find any other type(s).

Comments

2

NOTE: This example only works with a range that is MORE THAN ONE CELL. If the Range is only a single cell (1x1), Excel will treat it in a special way, and the range.Value2 will NOT return a 2-dimensional array, but instead will be a single value. It's these types of special cases that will drive you nuts, as well as zero and non-zero array lower bounds:

using Excel = Microsoft.Office.Interop.Excel;

private static void Test()
{
    Excel.Range range = Application.ActiveWorkbook.ActiveSheet.Range["A1:B2"]; // 2x2 array

    range.Cells[1, 2] = "Foo"; // Sets Cell A2 to "Foo"
    dynamic[,] excelArray = range.Value2 as dynamic[,]; // This is a very fast operation
    Console.Out.WriteLine(excelArray[1, 2]); // => Foo

    excelArray[1, 2] = "Bar";
    range.Value2 = excelArray; // Sets Cell A2 to "Bar", again a fast operation even for large arrays
    Console.Out.WriteLine(range.Cells[1, 2]); // => Bar

Note that excelArray will have row and column lower bounds of 1:

    Console.Out.WriteLine("RowLB: " + excelArray.GetLowerBound(0)); // => RowLB: 1
    Console.Out.WriteLine("ColLB: " + excelArray.GetLowerBound(1)); // => ColLB: 1

BUT, if you declare a newArray in C# and assign it, then the lower bounds will be 0, but it will still work:

    dynamic[,] newArray = new dynamic[2, 2]; // Same dimensions as "A1:B2" (2x2)
    newArray[0, 1] = "Foobar";
    range.Value2 = newArray; // Sets Cell A2 to "Foobar"
    Console.Out.WriteLine(range.Cells[1, 2]); // => Foobar

Fetching this value out of the range will give you the original array with lower bounds of 0:

    range.Cells[1, 2] = "Fubar";

    dynamic[,] lastArray = range.Value2 as dynamic[,];
    Console.Out.WriteLine(lastArray[0, 1]); // => Fubar

    Console.Out.WriteLine("RowLB: " + lastArray.GetLowerBound(0)); // => RowLB: 0
    Console.Out.WriteLine("ColLB: " + lastArray.GetLowerBound(1)); // => ColLB: 0
}

Working with Excel Interop can be daunting as there are many special cases like this in the codebase, but I hope this helps clarify at least this one.

Comments

1

The error means that the value is a string, so you can't convert it directly to array.

If the value is for example comma delimeted string you can use Split to get an array:

string[] MyRange = (range.Cells.Value + "").Split(',');
for (int k = 0; k < MyRange.Length; k++)
{
    //...loop here...
}

Also fixed your loop, you were going to get Index Out of Bounds error.

Comments

1

Late to the conversation, but here is a method to do this:

static string[][] GetStringArray(Object rangeValues)
{
    string[][] stringArray = null;

    Array array = rangeValues as Array;
    if (null != array)
    {
        int rank = array.Rank;
        if (rank > 1)
        {
            int rowCount = array.GetLength(0);
            int columnCount = array.GetUpperBound(1);

            stringArray = new string[rowCount][];

            for (int index = 0; index < rowCount; index++)
            {
                stringArray[index] = new string[columnCount];

                for (int index2 = 0; index2 < columnCount; index2++)
                {
                    Object obj = array.GetValue(index + 1, index2 + 1);
                    if (null != obj)
                    {
                        string value = obj.ToString();

                        stringArray[index][index2] = value;
                    }
                }
            }
        }
    }

    return stringArray;
}

called with:

string[][] rows = GetStringArray(range.Cells.Value2);

2 Comments

stringArray[index] = new string[columnCount-1]; is wrong i get index out of range. Correct is stringArray[index] = new string[columnCount];
@LévayLőrinc - Thanks for that catch. I've updated the sample code accordingly.
0

The issue is because ,when your range becomes a single cell ,and "range.value"/"range.cell.value" means String value,this string cannot be put into object array. So check if your range has only one cell or more and do according to that

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.