.NET数组下限大于0

13

虽然这可能是一个奇怪的需求,但我需要在 .Net 中创建一个下限 > 0 的数组。起初似乎可以通过以下方式实现:

Array.CreateInstance(typeof(Object), new int[] {2}, new int[] {9});

生成所需的结果(一个下边界设置为9的对象数组)。但是,创建的数组实例不能再传递给期望Object[]的其他方法,否则会出现错误,如下所示:

System.Object[*] 无法强制转换为 System.Object[]。这些数组类型之间的区别是什么,我该如何克服这个问题?

编辑:测试代码 =

Object x = Array.CreateInstance(typeof(Object), new int[] {2}, new int[] {9});
Object[] y = (Object[])x;

该操作失败,提示信息为:“无法将类型为 'System.Object[*]' 的对象强制转换为类型 'System.Object[]'。”

我还想指出的是,当使用多个维度时,这种方法确实有效

Object x = Array.CreateInstance(typeof(Object), new int[] {2,2}, new int[] {9,9});
Object[,] y = (Object[,])x;

这很好用。

5个回答

3
你无法从一个类型向另一个类型转换的原因是这样做是不好的。
比如说你创建了一个对象数组[5..9],并将其作为对象[]传递给函数F。
函数如何知道这是一个5..9的数组?F期望一个通用数组,但它得到了一个约束数组。你可以说它可能知道,但这仍然是不可预期的,人们不想每次使用简单数组时都进行各种边界检查。
数组是编程中最简单的结构,过于复杂会使其难以使用。你可能需要另一种结构。
你可以创建一个类来模拟所需行为的约束集合。这样,该类的所有用户都将知道该期望什么。
class ConstrainedArray<T> : IEnumerable<T> where T : new()
{
    public ConstrainedArray(int min, int max)
    {
        array = new T[max - min];
    }

    public T this [int index]
    {
        get { return array[index - Min]; }
        set { array[index - Min] = value; }
    }

    public int Min {get; private set;}
    public int Max {get; private set;}

    T[] array;

    public IEnumerator<T> GetEnumerator()
    {
        return array.GetEnumarator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return array.GetEnumarator();
    }

}

感谢提供代码片段,但我希望在必要时避免任何类型的包装,因为这个问题出现在VB6迁移问题中,大量的代码期望下限> 0。因此,我正在尽力确定是否有可能不使用自定义类。 - DAC
虽然你对特定情况的解释很有道理,但我并不完全同意你的措辞。作为一个从Pascal背景转到.NET的人来说,“一般数组”或“简单数组”对我来说总是具有任意起始索引的东西。C#看待这个问题的方式不同,但这绝不能成为在像.NET这样的多语言平台上省略“各种边界检查”的借口。毕竟,在C#中也需要对数组长度(隐含地,允许的索引值的上限)进行边界检查。尽管如此,这是一个很好的包装类。 - O. R. Mapper
GetEnumerator 出现了一个拼写错误:GetEnumarator。 - as9876

1

我不确定为什么不能将它作为Object[]传递,但如果您只是创建一个真正的类来包装数组并在其中处理您的“奇怪逻辑”,那么这不会很容易吗?

您将获得使用真正引用对象的好处,可以向您的类添加“智能”。

编辑:您如何转换您的数组,请发布更多代码?谢谢。


封装是很好的,但出于某些原因和在某些情况下需要避免。我会编辑并添加更多代码。 - DAC

1

只需将下限存储在一个常量偏移整数中,并从源返回的索引值中减去该值。

另外:这是一个旧的VB6功能。我认为可能有一个属性来帮助支持它。


1
.NET CLR区分两种内部数组对象格式:SZ数组MZ数组。MZ数组可以是多维的,它们将其下限存储在对象中。
这种差异的原因有两个:
1. 为单维数组生成有效代码需要没有下限。具有下限是极不常见的。我们不想牺牲常见情况下的显着性能来使用这个很少用到的功能。
2. 大多数代码希望零下限数组。我们当然不想污染所有代码并检查下限或调整循环边界。
通过为SZ数组制作单独的CLR类型来解决这些问题。这是几乎所有实际出现的数组使用的类型。

但是我该如何传递一个 Object[*] 或者 Object[9..11]?如果我尝试声明它,会导致编译错误,而 Array 又太过于不具体... - undefined

0

我知道这是一个老问题,但为了充分解释它。

如果类型(在本例中是一个下界大于0的单维数组)不能通过键入的代码创建,则无法通过键入的代码使用反射类型实例。

你已经注意到的内容已经在文档中了:

https://learn.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names

请注意,从运行时的角度来看,MyArray[] != MyArray[*],但对于多维数组而言,这两种表示法是等效的。也就是说,Type.GetType("MyArray [,]") == Type.GetType("MyArray[*,*]") 的结果为 true。
在 C# / VB / ... 中,您可以将反射数组保留在对象中,作为对象传递,并仅使用反射访问其项。

-

现在你可能会问:“为什么还需要LowerBound呢?”其实COM对象并不是.NET的,它们可以用旧的VB6编写,而VB6有一个数组对象,它的LowerBound设置为1(或者任何VB6有这种自由或诅咒,这取决于你问谁)。要访问这样的对象的第一个元素,你实际上需要使用“comObject(1)”而不是“comObject(0)”。因此,检查下限的原因是当你对这样的对象进行枚举时,要知道从哪里开始枚举,因为COM对象中的元素函数期望第一个元素是LowerBound值,而不是零(0),在这些情况下支持相同的逻辑是合理的。想象一下,如果你将索引值为1甚至2001的元素实例传递给某个Com对象的方法,并尝试获取第一个元素的值为0,那么代码将非常混乱。
简单来说:这主要是为了支持遗留系统!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接