如何在 C# 中创建索引从 1 开始的一维数组
Posted
技术标签:
【中文标题】如何在 C# 中创建索引从 1 开始的一维数组【英文标题】:How to create a 1-Dimensional Array in C# with index starting at 1 【发布时间】:2011-08-09 08:41:34 【问题描述】:对于多维数组 Array.CreateInstance 可用于创建基于非零索引的数组,但如果您尝试将其用于一维数组(向量),例如:
public double[] myArray = (double[])Array.CreateInstance(typeof(double), new int[1] 12 , new int[1] 1 );
当从多维数组转换为一维数组失败时,这将在运行时失败
"Unable to cast object of type 'System.Double[*]' to type 'System.Double[]'"
现在我可以创建一个基于零的数组并忽略第一个值,或者使用偏移量等,但是我是否忽略了一些允许非零基向量的 c# 语法魔法?
更新:
如果他说“There's no obvious way to make a non-zero-based array in C#”,我会接受Eric Lippert 的话
【问题讨论】:
你可以制作一个简单的包装结构,它包装一个从零开始的数组,并有一个索引器可以做你想做的事情——添加或减去正确的偏移量。 嗨,我不同意 Genady 的回答(您标记为“最正确”)。请您看看我刚刚添加的答案,并取消标记 Genady 的答案,因为它不正确。 【参考方案1】:您可以在 C# 中创建一个非从零开始的数组,但它的用法有点令人讨厌。它绝对不是普通(即从零开始的单维)数组的简单替代品。
// Create the array.
Array myArray = Array.CreateInstance(typeof(double), new int[1] 12 , new int[1] 1 );
// Fill the array with random values.
Random rand = new Random();
for (int index = myArray.GetLowerBound(0); index <= myArray.GetUpperBound(0); index++)
myArray.SetValue(rand.NextDouble(), index);
// Display the values.
for (int index = myArray.GetLowerBound(0); index <= myArray.GetUpperBound(0); index++)
Console.WriteLine("myArray[0] = 1", index, myArray.GetValue(index));
为此所需的 GetValue/SetValue 语法比每次出现时从向量索引中减去一个更难看。
如果一个值类型存储在数组中,那么它将被存储在连续位置,就像在常规数组中一样,但是 getter 和 setter 需要对值进行装箱(除非有一些我不具备的编译器魔法意识到)。而 getter 通常需要强制转换(只是为了让它更丑)。
double myValue = (double)myArray.GetValue(index);
另请注意,GetUpperBound
的正确比较是 <=
,不像 Length
与 <
比较。
【讨论】:
天哪,这太糟糕了。我在 LINQ 上下文中尝试了这个。编译器接受这个带有两个 int[] 的特殊构造函数,但是生成的 Array 对象没有任何神奇的 LINQ 方法。 (至少在 .NET 4 上。)感谢您的分享。 @StéphaneGourichon 为了使用 LINQ,您需要将Array
类型转换为 IEnumerableC 中确实存在基于非零的数组,并且有一种方法可以创建基于 1(或其他)的数组。
我完全同意它们是杂乱无章的,它们不应该用于遗留东西以外的任何东西,但它们对于与旧的 COM 库进行交互是必不可少的。
遇到这种情况的最常见的地方是使用 Excel 库中的 Microsoft.Office.Interop.Excel.Range 对象,它仍然使用下面的旧 DCOM 接口。
例子:
/// <summary>
/// Makes the equivalent of a local Excel range that can be populated
/// without leaving .net
/// </summary>
/// <param name="iRows">number of rows in the table</param>
/// <param name="iCols">number of columns in the table</param>
/// <returns>a 1's based, 2 dimensional object array which can put back to Excel in one DCOM call.</returns>
public static object[,] NewObjectArray(int iRows, int iCols)
int[] aiLowerBounds = new int[] 1, 1 ;
int[] aiLengths = new int[] iRows, iCols;
return (object[,])Array.CreateInstance(typeof(object), aiLengths, aiLowerBounds);
在这种情况下,需要此代码的原因是每个对 excel 的 DCOM 调用都是跨进程调用,如果您要一次访问一个单元格,则会产生巨大的开销,(检索或设置值)。 Excel 范围是一个基于 1 的二维数组,如果创建数组并在本地填充它,则可以在一次跨进程调用中将其推送到 Excel,从而极大地提高性能。
【讨论】:
谢谢,Excel 正是我现在遇到这个问题的地方。【参考方案3】:如果有人还在寻找基于 1 的数组实现,这里是一个一维通用的基于 1 的数组,它只是常规数组的包装类型:
https://github.com/ColmBhandal/CsharpExtras/blob/master/CsharpExtras/Enumerable/OneBased/OneBasedArray.cs
还有一个二维版本,但目前还没有更高维度的版本(此代码是为支持 Excel 而编写的,所以只需要二维):
https://github.com/ColmBhandal/CsharpExtras/blob/master/CsharpExtras/Enumerable/OneBased/OneBasedArray2D.cs
这两种类型都在同一个 repo 下的关联测试项目中为它们编写了测试:
https://github.com/ColmBhandal/CsharpExtras/tree/master/CsharpExtrasTest/OneBased
免责声明:这些存储库托管在我的个人 GitHub 帐户上并且是开源的。
【讨论】:
【参考方案4】:C# 中的所有数组都是从零开始的。据我所知,没有办法创建一个基于 1 的数组。想象一下,如果可能的话,会发生什么样的混乱。这是一个类似的线程,它更详细地解释了这个问题 - C#: Nonzero-based arrays are not CLS-compliant
【讨论】:
实际上,您可以在 c# 中创建基于 1 的数组,请参阅下面的最高投票答案。 @GenadySergeev 我同意拥有两种数组会一团糟。但是基本索引可以是任何东西的帕斯卡风格的数组实际上非常有用并且不混乱。有趣的是这个世界是如何运作的。 我对此投了反对票,因为这是一个危险的错误答案。 Dot Net 框架支持基于非零的数组,以保持与旧 COM/DCOM 组件的遗留兼容性。特别是 Excel.Range 对象的必要本地副本需要一个基于 1 的二维数组。可以使用此处的 Array.CreateInstance() 文档将数组的维度设为非零:msdn.microsoft.com/en-us/library/x836773a(v=vs.110).aspx 事实上,当您访问 Worksheet.UsedRange.Value 时,Excel 的 COM 互操作会返回一个基于非零的多维数组。 完全错误的说法。请参阅 Jeffrey L Whitledge 的答案。以上是关于如何在 C# 中创建索引从 1 开始的一维数组的主要内容,如果未能解决你的问题,请参考以下文章