1

I understand:

  • Range.Item returns a Range object that represents a range at an offset to the specified range.

  • Range.Cells returns a Range object that represents the cells in the specified range.

However, the confusion sets in when I read that "Item is a default property of the Range object".

This leads me to ask:

Is Range("A1:D5").Cells(1,1) really a simplified version of Range("A1:D5").Cells.Item(1,1)?

And why not just use Range("A1:D5").Item(1,1) in any case?

Why does it seem that VBA programmers use the Cells property to reference ranges instead of the Item property? Is it "best practice" to just omit the Item default property in favor of Cells?

Mathieu Guindon
  • 69,817
  • 8
  • 107
  • 235
GeekyFreaky
  • 283
  • 1
  • 2
  • 9
  • have a look at [SO: Range() vs Cells() vs Range.Cells()](https://stackoverflow.com/questions/51093274/range-vs-cells-vs-range-cells) as there are answers on there that may help – Our Man in Bananas Aug 22 '18 at 15:40
  • also see [Understanding Excel Cells vs. Range Functions in VBA](https://www.makeuseof.com/tag/excel-cells-vs-range-functions-vba/) – Our Man in Bananas Aug 22 '18 at 15:41
  • Possible duplicate of [SO: Range() vs Cells() vs Range.Cells()](https://stackoverflow.com/questions/51093274/range-vs-cells-vs-range-cells) – Our Man in Bananas Aug 22 '18 at 15:42
  • 2
    Possible duplicate of [What's the difference between Range.Item and Range.Cells?](https://stackoverflow.com/questions/25879619/whats-the-difference-between-range-item-and-range-cells) – Comintern Aug 22 '18 at 15:47
  • Thanks, everyone, but none of the articles clarified it for me. Is there anyone who can just answer the questions I asked with Yes or No - and then explain? LOL – GeekyFreaky Aug 22 '18 at 16:01
  • 1
    No. `Range.Item(x, y)` returns a `Variant`. If you need a `Range`, use `Cells` so it doesn't need to be un-boxed into an object. – Comintern Aug 22 '18 at 16:10

1 Answers1

5

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:

Property _Default({RowIndex},{ColumnIndex})

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:

Property Cells As Range

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.

Mathieu Guindon
  • 69,817
  • 8
  • 107
  • 235
  • OMG, thank you for this wonderful explanation. I get it! Mathieu, you rock. You answered my last question too.^^ – GeekyFreaky Aug 22 '18 at 16:36
  • foo = Sheet1.Range("A1:A10")(1) - is that not supposed to have a period before (1)? – GeekyFreaky Aug 22 '18 at 19:10
  • 1
    @GeekyFreaky no, because the dot (aka dereferencing oeprator) would warrant an explicit member call immediately after it. Since the member call is implicit, there's no dot. With the dot that would be `Sheet1.Range("A1:A10").Item(1)`, or `.Cells(1)`. `Sheet1.Range("A1:A10")` returns a `Range` object, so you can legally make an implicit default member call directly against it. Now whether that's a good idea is another story ;-) – Mathieu Guindon Aug 22 '18 at 19:12