Range
is a class. Classes are blueprints for objects - an object is an instance of a class.
Now, classes in VBA can have a default property. If you export a class module, you can give them member attributes. The VB_UserMemId
attribute, with a value of 0
, identifies a member as that class' default member - you can't edit member attributes directly in the VBE, but you can export the module, add the attribute, and re-import the module into the project.
A default property might look something like this:
Option Explicit
Private mFoo As Bar
Public Property Get Foo() As Bar
Attribute Foo.VB_UserMemId = 0
Set Foo = mFoo
End Property
There are a number of possible VB_UserMemId
values you can use, but only one member of a class can have any given value - 0
makes it that class' default member.
Default members allow you to write implicit code - whether that's a good thing or not is a judgement call.
For example when you want to access the item at index i
of a Collection
, you can do this:
foo = myCollection.Item(i)
But since Item
is the default property of the Collection
class, you can also do this:
foo = myCollection(i)
And the result is exactly the same.
The Range
class is... a special animal. If you look at its definition in the Object Browser, you'll find that its default property is a hidden property named _Default
, which is commonly inferred to return Range.Value
- because this implicit default member call:
foo = Sheet1.Range("A1")
Is equivalent to this explicit code:
foo = Sheet1.Range("A1").Value
The Item
property of the Range
class is not the default member. However if you look at the definition of that hidden _Default
property, you'll see that it accepts two optional parameters:

So this is also legal:
foo = Sheet1.Range("A1:A10")(1)
And equivalent to this:
foo = Sheet1.Range("A1:A10").Item(1).Value
Basically, the Range
class' default property is implemented in such a way that it does one thing if no parameters are specified (gives you the Value
.. which itself returns either the cell's value, or a 2D variant array if the range contains multiple cells), and another thing if parameters are specified (gives you the Item
at the specified row/column within that range).
So this is why Range("A1:A10").Cells
(without parameters) is redundant: it yields the exact same cells as Range("A1:A10")
does.
The Cells
property isn't parameterized:

So VBA programmers invoking Range.Cells
with parameters, are in fact invoking Range._Default
with these parameters, against the range returned by Cells
... which means Range("A1:A10").Cells(1)
is effectively Range("A1:A10").Cells.Item(1)
... which involves a redundant call to Cells
.
The parameterized Cells[._Default][.Item](1)
syntax is therefore a mere convenience; it's mostly a readability thing... and then there's IntelliSense: Item
returns a Variant
, which means you don't get that nice little dropdown of available members when you type the .
dot/dereferencing operator after it (the call is "late-bound", i.e. the type involved is resolved at run-time) - but you do when you invoke Cells
, because Cells
returns an early-bound Range
object (i.e. resolved at compile-time).
Best-practice is to write code that does what it says, and says what it does - so you will generally want to avoid implicit default member calls. However in the case of a Range
this conflicts with another best-practice, i.e. write compile-time validated, early-bound code whenever possible - using Cells
instead of Item
gives you that.