Jon Skeet said:
It makes perfect sense. The first one has defined a variable of type
"reference to array of integers", and has implicitly set the value to
nothing.
The second one has defined a variable of type "reference to array of
integers", and has explicitly set the value to be a reference to an
empty array.
The reason I personally think its not logical...hmm, actually it is
logical, its just not friendly so to speak. Simply defining the
default value of an array to be a zero length array, such that:
Dim arr1() As Integer
is synonymous with:
Dim arr1() As Integer = {}
would, IMO, be more practical.
Arrays are reference types in all respects - how are they "half way" to
being value types?
For instance, you are not required to use the New operator and can do:
Dim arr() As Integer = {1, 2, 3}
instead of:
Dim arr() As Integer = New Integer() {1, 2, 3}
This absence of New in the first syntax gives no indication that
arrays are indeed reference types and are allocated on the heap. It
makes it appear as if arrays are stack allocated and are hence value
types. A similar thing can be said about the New operator and
structures.
My reasoning for this is based on my understanding of the semantics of
the New operator both from VB6 and other OOP languages like C++ and
Java, but YMMV.
(Note: that syntactical allowance is done for convenience and I am
glad its allowed. For other syntactical exceptions that have to be
done to make type unification viable, see concepts such as "manifest
classes" in section 3.3 of the Blue language paper at
http://tinyurl.com/yryyz )
Yes, but if arrays weren't objects there's be any number of other
problems with them. Passing them around would be a nightmare, for
instance - you'd have to pass them by reference all over the place just
for the sake of performance. Yuk.
I agree but there is a way to implement them to satisfy both the
performance criteria and the avoidance of NullReferenceException.
There are two parts to an array. The descriptor, which contains type
information on the array (number of dimensions, size of each
dimension, pointer to data, etc) and the actual array data. If you
allocate the descriptor on the stack and the data on the heap the
problem vanishes. One can check the array type info without getting
exceptions because the descriptor is stack allocated, and passing them
around can be defined as copying the descriptor rather than the data
hence removing the performance penalty.
Furthermore, since ReDim can't change the number of dimensions for an
array in VB.NET, the above proposal of stack allocating the descriptor
works well. In VB6, the descriptor would need to be allocated on the
heap (due to ReDim's behaviour) otherwise the compiler writer will
need some serious black magic to implement dynamic allocation within
the current procedure's stack frame.
Unless there is an array, there *is* no upper bound though - and an
array always has all its possible elements, even if some of them are
null/nothing. In your previous example, arr1.Length wasn't saying "what
is the length of the array which is arr1's value", it's saying "what is
the length of the array which arr1's value refers to" - and given that
arr1's value *doesn't* refer to an array, it's natural that there
should be an exception.
Natural? Yes. Convenient and efficient? Not IMO. (See previous
comments in this same post).
It's important to distinguish between the idea of the value of a
variable being an actual array (which it isn't) and it being either a
reference to an array, or nothing.
Agreed. One thing though: it can be quite convenient if the variable
is the actual array (i.e the array descriptor and data are stack
allocated) and the performance gains for small, fixed-size arrays
(such as the 3x3 transformation matrices used in graphics programming)
can be very substantial.
You shouldn't usually be really *worrying* about
NullReferenceExceptions though - either null is a valid value for the
variable at that point in time, in which case it may well have a
special meaning as distinct from an empty array, or it may not be
valid, in which case throwing an exception is perfectly reasonable
(although you may wish to make the check at the start of the method if
it's coming in as a parameter, for instance).
You are right that I shouldn't have to be worrying about
NullReferenceExceptions but with the current implementation, I have
no choice but to worry. Even if I do check incoming parameters,
VB.NET doesn't allow declarations of fixed-size arrays and I can wipe
out the array at any time and later get into trouble:
Sub Bar(ByVal arr() As Integer)
If arr Is Nothing Then
'take appropriate action
End If
'some code here
Erase arr
'some more code here
For n As Integer = 0 To arr.Length - 1
'blah
Next
End Sub
The For loop fails with a NullReferenceException because of using the
Length property on a null object. To avoid that, I either have to
ensure that an array is never wiped out (an endeavour that will fail
atleast once) or litter my code with "If" statements to check for the
possibility each time I want to access an instance member of the Array
class.
Given the above (and my previous post), I'm afraid I have to disagree
with you. The suggestion that all types are reference types is the
biggest problem, IMO.
I should have been clearer initially, about the conceptual and
implementation aspects when giving my explanation. My bad.
Cheers,
Eric