In porting C# code to Objo, I'm encountering situations where in C# I used actual fixed-length typed arrays. Sometimes this is the better metaphor: fixed-size buffers, short lists of values that might not be assigned in ordinal order, etc. Objo arrays are really List<T>s under the hood, which takes a smidgen of getting used to but is fine for most situations.
However sometimes I do want an Array(Of T) to be pre-initialized to a known size and pre-populated with default values (zero for numerics, Nothing for reference types, DateTime.MinValue for dates, etc) so that I can then very rapidly read or write individual values by index without worrying about whether there's anything there in the first place.
I thought about several possible approaches to this issue and to my mind the cleanest / simplest solution that would cover most of the use cases I can anticipate, would be an optional count argument to the constructor:
Var foo = new Array(Of Integer)(10)
This would set capacity to 10, populate indexes 0-9, and set Count of course to 10. After that, one uses the Array however one wishes, including potentially making Append() calls later. But ordinarily you would do this so you could just address individual rows for reads/writes by index, knowing that the size is pre-initialized to what you need.
At present I have an ArrayHelper class that prepopulates an array for me but for the above example it has to do 10 Append() calls in a loop, which is awkward / inconvenient for the use case and probably significantly slower than the VM doing or delegating to the .NET runtime to do it. This becomes more of a concern with large arrays. My largest so far is 8,192 elements, although only initialized once per instance and so not a hot path concern. Later, that might not be the case so much.
Instead of the constructor argument (or IMO, in addition to it) you could have a Resize() method with a couple of overloads that would provide maximum flexibility and power. It would work as follows:
Var foo As New Array(Of Integer)
foo.Resize(10) # same as the optional constructor in this situation, but could be called on a non-empty array
# or:
foo.Resize(10,42) # initialize all elements to something other than the type default (zero in this case). This is arguably the least important new functionality, but it covers all the bases.
The rules would be:
- If the requested # of elements is > current Count property, add those elements to whatever is already there. If Capacity is a smaller number, Reserve(Count) first. Only new elements are initialized.
- If the requested # of elements is < current Count property, truncate to that size. Leave Capacity alone; the caller can always TrimExcess() if they feel the need.
- If the requested # of elements is = current Count property, it's a no-op.
The argument against a Resize() method is that it makes the API for Array(Of T) too fussy or confusing for some users. Although I think it's pretty clear what you would use it for, maybe it's too similar to ReserveCapacity() and you'd get bogged down in explaining the difference (though I don't personally buy that argument). Functionally, my helper method already does this, but again, if you're making a big increase or decrease in size you are faced with repeated Append() or RemoveAt() calls, and that is slower.
I supposed that Resize() could also have a boolean to specify a forced overwrite of all elements with the default or specified value, so you have a clean slate. But one could also split the responsibilities into two methods, like Resize() and ReInitialize(). Yet Another Method, but also maybe clearer / easier to document and explain.
I spitballed other possibilities too -- e.g., make Count writable, or have a separate FixedArray(Of T) class. IMO the optional constructor argument on the existing Array(Of T) is simple and clean and easy to explain, Resize() (or Resize() and ReInitialize()) would be nice to have, and all that is a smaller blast radius than the alternatives.