我使用
Microsoft.Office.Interop.Excel
我返回了一个 object[,]
类型的二维数组,其中包含 double
元素。请注意,索引下限是 1
,而不是默认的 0
,但我可以轻松处理。
如何使用 .NET 3.5 nicely 将数组转换为
double[,]
。 (很好,我的意思是简洁或紧凑)。
请注意
double[] values_2 = values.Cast<double>().ToArray();
确实有效,但它通过数组展平为一维结构。
object[,] src = new object[2, 3];
// Initialize src with test doubles.
src[0, 0] = 1.0;
src[0, 1] = 2.0;
src[0, 2] = 3.0;
src[1, 0] = 4.0;
src[1, 1] = 5.0;
src[1, 2] = 6.0;
double[,] dst = new double[src.GetLength(0), src.GetLength(1)];
Array.Copy(src, dst, src.Length);
我不会说有一种方法比另一种方法更快,只要你不做任何愚蠢的事情。我想说的是,如果可以的话,在访问它们时就投射它们,而不是预先投射它们。当然,这取决于您打算如何访问它们。如果您要多次对数组进行索引,那么拆箱的成本可能会开始变得过高。如果您只扫描一次数组,那么就可以随心所欲地进行投射。
这应该在大多数情况下有效,但如果您不分配转换委托,可能会引发异常。
public static TResult[,] Convert<TSource, TResult>(
this TSource[,] array, Func<TSource, TResult> conversion = null) {
if(array == null) throw new ArgumentNullException("array");
if (conversion == null) {
var resultType = typeof(TResult);
conversion = source => (TResult)System.Convert.ChangeType(source, resultType);
}
var width = array.GetLength(1);
var height = array.GetLength(0);
var result = new TResult[height, width];
for (int i = 0; i < height; ++i)
for (int j = 0; j < width; ++j)
result[i, j] = conversion(array[i, j]);
return result;
}
Array.Copy(src, dst, src.Length);
如果
src 中的任何value
为空,此代码将会出错。
由于在上面的代码中src有一个定义的值,它工作得很好。
如果 src 的值是动态设置的,不幸的是,如果其中任何一个值为 null,则上述代码将不起作用,因为
value
将无法成功复制。
这里有几个问题。
首先,由于
double
不是引用类型,因此必须装箱才能存储在 object[] 中,因此获取值的唯一方法是将值拆箱到 double[] (复制副本)中。
另一个问题是,在 C# 中,数组是协变的,但不是逆变的,您可以将数组分配给派生程度较高的类型的引用,而不是派生程度较低的类型的引用。
string[] strings = new string[10];
object[] objects = strings; // OK, covariant
string[] strings2 = objects; // not OK, contravariant
objects[0] = 10; // Compiles fine, runtime ArrayTypeMismatchException!
只是想添加一个关于通过互操作从 Excel 读取数组的有用想法。我总是使用自定义类(下面的代码)来强制 Excel 交出看起来像基数为零的数组,以防止我的更广泛的代码在基数之间混淆。
在下面的示例中,我实际上使用了两个构造函数来捕获从 Excel 读取的单单元格范围的特殊情况,因为 Excel 很烦人并且在这种情况下不会移交数组。这样,ExcelData 类就会创建一个 1×1 数组,这样当遇到单个单元格时,其余代码就不会中断!
public class ExcelData
{ // This class wraps an Excel range read - it will either be created by:
// 1. An object[,] base-1 array from Excel stored as excelData[1..rows,1..cols]
// 2. An object scalar value from Excel stored as _excelData[0,0]
// In either case, the 'this' identifier allows access as if it was a base-0 array.
private object[,] _excelData;
private bool _baseZero;
public ExcelData(object[,] excelData)
{ // Constructor for usual case of receiving a base-1 object[,] from Excel
this._excelData = excelData;
this._baseZero = false;
}
public ExcelData(object excelData)
{ // Constructor for special case where only single value is returned
this._excelData = new object[1,1];
this._excelData[0, 0] = excelData;
this._baseZero = true;
}
public object this[int x, int y]
{ // Returns the correct element on a zero-basis
get
{
if (this._baseZero)
{ return this._excelData[x, y]; }
else
{ return this._excelData[x + 1, y + 1]; }
}
set
{
if (this._baseZero)
{ this._excelData[x, y] = value; }
else
{ this._excelData[x + 1, y + 1] = value; }
}
}
public int RowCount { get { return _excelData.GetLength(0); } }
public int ColCount { get { return _excelData.GetLength(1); } }
}
当然,如果您愿意,您可以修改上面的内容以显式添加属性来读取特定类型的数据。这样,您可以将数据存储为对象 [*,*],就像 Excel 为您提供(并期望返回)一样,但在使用 Get/Set 方法检索(或更新)数据时将其视为不同类型的数组。