Querying objects

TL;DR

This page provides a brief overview of ways to reflectively query objects, namely accessing object pointers , accessing object state , and accessing an object's class .

What is an object?

There are three key aspects of an object in Smalltalk:

1. An object is a reference ( i.e. , a “pointer”) to a piece of memory in the image.

2. An object contains values . These may be instance variables, indexed (array) values, or, in the degenerate case of a SmallInteger Integer immediateSubclass: #SmallInteger instanceVariableNames: '' classVariableNames: '' package: 'Kernel-Numbers' , the reference itself is the value.

3. An object is an instance of a class.

Accessing object pointers

Normally you won't care what the actual pointer value of an object is, but you might like to know what points to an object you have in your hand.

Here we find all the pointers to a new instance of GtLudoGame Object subclass: #GtLudoGame instanceVariableNames: 'players squares startSquares goalSquares die announcer feedback winner needToRollDie lastDieRolled playerQueue routeCache' classVariableNames: '' package: 'GToolkit-Demo-Ludo-Model' .

GtLudoGame new pointersTo.
  

This shows us that an instance of a Ludo game consists of numerous components that point back to the game instance.

This and related methods can be useful, for example, for debugging memory leaks. See the pointing to protocol of ProtoObject ProtoObject subclass: #ProtoObject instanceVariableNames: '' classVariableNames: '' package: 'Kernel-Objects'. ProtoObject superclass: nil for some other pointer querying methods.

Accessing object state

Recall that the slots of an object are private to that object. If accessors are not provided, then no other object, including other objects of the same class, and even the class of the object, cannot read or write the slots of that object. (For this reason object initialization in Smalltalk is a bit convolution, with instance creation always utilizing instance-side methods to initialize a new object's state.)

With reflection, however, we can circumvent this restriction, even if an object provides no accessors.

For example, an instance of GtLudoRecordingGame GtLudoGame subclass: #GtLudoRecordingGame instanceVariableNames: 'moves' classVariableNames: '' package: 'GToolkit-Demo-Ludo-Model' has a #routeCache slot, but no accessor. We can nevertheless access it programmatically with the help of Object>>#instVarNamed: instVarNamed: aStringOrSymbol "Return the value of the instance variable in me with that name. Slow, but very useful. We support here all slots (even non indexed) but raise a backward compatible exception" ^ self class slotNamed: aStringOrSymbol ifFound: [ :slot | slot read: self ] ifNone: [ InstanceVariableNotFound signalFor: aStringOrSymbol asString ] .

game := GtLudoRecordingGame new autoPlay: 10.
game instVarNamed: #routeCache.
  

We can also change an object's state with Object>>#instVarNamed:put: instVarNamed: aString put: aValue "Store into the value of the instance variable in me of that name. Slow, but very useful. We support here all slots (even non indexed) but raise a backward compatible exception" ^ self class slotNamed: aString ifFound: [ :slot | slot write: aValue to: self ] ifNone: [ InstanceVariableNotFound signalFor: aString asString ] .

You can similarly access indexed data as follows:

'Hello' instVarAt: 5.
  

Note that these methods are classified in the introspection protocol of Object ProtoObject subclass: #Object instanceVariableNames: '' classVariableNames: 'DependentsFields' package: 'Kernel-Objects' .

Accessing an object's state in this way can be useful for building tools or analyses (such as an Inspector), but for normal applications it is advisable to create dedicated accessors instead.

Accessing an object's class

To get the class of an object, just send it the message class:

game := GtLudoRecordingGameExamples new gameShowingAllMoves6.
game class.
  

Note that this returns a reification of the object's class, that is, a metaobject called GtLudoRecordingGame GtLudoGame subclass: #GtLudoRecordingGame instanceVariableNames: 'moves' classVariableNames: '' package: 'GToolkit-Demo-Ludo-Model' . This is for all intents and purposes the object's class, but it is not the actual running class in the virtual machine. Since the two are causally connected (changes to the one will affect the other), we don't really notice the difference.