Working with Registered Objects
The %RegisteredObjectOpens in a new tab class is the basic object API in InterSystems IRIS® data platform. This topic describes how to use this API. Information in this topic applies to all subclasses of %RegisteredObjectOpens in a new tab.
Introduction to Object Classes
An object class is any class that inherits from %RegisteredObjectOpens in a new tab. With an object class, you can do the following things:
-
Create instances of the class. These instances are known as objects.
-
Set properties of those objects.
-
Invoke methods of those objects (instance methods).
These tasks are possible only with object classes.
The classes %PersistentOpens in a new tab and %SerialObjectOpens in a new tab are subclasses of %RegisteredObjectOpens in a new tab. For an overview, see Object Classes.
OREF Basics
When you create an object, the system creates an in-memory structure, which holds information about that object, and also creates an OREF (object reference), which is a pointer to that structure.
The object classes provide several methods that create OREFs. When you work with any of the object classes, you use OREFs extensively. You use them when you specify values of properties of an object, access values of properties of the object, and call instance methods of the object. Consider the following example:
MYNAMESPACE>set person=##class(Sample.Person).%New()
MYNAMESPACE>set person.Name="Carter,Jacob N."
MYNAMESPACE>do person.PrintPerson()
Name: Carter,Jacob N.
In the first step, we call the %New() method of a class named Sample.Person; this creates an object and returns an OREF that points to that object. We set the variable person equal to this OREF. In the next step, we set the Name property of object. In the third step, we invoke the PrintPerson() instance method of the object. (Note that the Name property and the PrintPerson() method are both just examples—these are defined in the Sample.Person class but are not part of the general object interface.)
An OREF is transient; the value exists only while the object is in memory and is not guaranteed to be constant over different invocations.
An OREF is only valid within the namespace where it was created; hence, if there are existing OREFs and the current namespace changes, any OREF from the previous namespace is no longer valid. If you attempt to use OREFs from other namespaces, there might not be an immediate error, but the results cannot be considered valid or usable, and may cause disastrous results in the current namespace.
INVALID OREF Error
In simple expressions, if you try to set a property, access a property, or invoke an instance method of a variable that is not an OREF, you receive an <INVALID OREF> error. For example:
MYNAMESPACE>write p2.PrintPerson()
WRITE p2.PrintPerson()
^
<INVALID OREF>
MYNAMESPACE>set p2.Name="Dixby,Jase"
SET p2.Name="Dixby,Jase"
^
<INVALID OREF>
The details are different when the expression has a chain of OREFs; see Introduction to Dot Syntax.
Testing an OREF
InterSystems IRIS provides a function, $ISOBJECT, which you can use to test whether a given variable holds an OREF. This function returns 1 if the variable contains an OREF and returns 0 otherwise. If there is an chance that a given variable might not contain an OREF, it is good practice to use this function before trying to set a property, access a property, or invoke an instance method of the variable.
OREFs, Scope, and Memory
Any given OREF is a pointer to an in-memory object to which other OREFs might also point. That is, the OREF (which is a variable) is distinct from the in-memory object (although, in practice, the terms OREF and object are often used interchangeably).
InterSystems IRIS manages the in-memory structure automatically as follows. For each in-memory object, InterSystems IRIS maintains a reference count — the number of references to that object. Whenever you set a variable or object property to refer to a object, its reference count is automatically incremented. When a variable stops referring to an object (if it goes out of scope, is killed, or is set to a new value), the reference count for that object is decremented. When this count goes to 0, the object is automatically destroyed (removed from memory) and its %OnClose() method (if present) is called.
For example, consider the following method:
Method Test()
{
Set person = ##class(Sample.Person).%OpenId(1)
Set person = ##class(Sample.Person).%OpenId(2)
}
This method creates an instance of Sample.Person and places a reference to it into the variable person. Then it creates another instance of Sample.Person and replaces the value of person with a reference to it. At this point, the first object is no longer referred to and is destroyed. At the end of the method, person goes out of scope and the second object is destroyed.
Removing an OREF
If needed, to remove an OREF, use the KILL command:
kill OREF
Where OREF is a variable that contains an OREF. This command removes the variable. If there are no further references to the object, this command also removes the object from memory, as discussed earlier.
OREFs, the SET Command, and System Functions
For some system functions (for example, $Piece, $Extract, and $List), InterSystems IRIS supports an alternative syntax that you can use to modify an existing value. This syntax combines the function with the SET command as follows:
SET function_expression = value
Where function_expression is a call to the system function, with arguments, and value is a value. For example, the following statement sets the first part of the colorlist string equal to "Magenta":
SET $PIECE(colorlist,",",1)="Magenta"
It is not supported to modify OREFs or their properties in this way.
Also, $DATA(), $GET(), and $INCREMENT() can only take a property as the argument if the property is multidimensional. These operations can be done within an object method using the instance variable syntax i%PropertyName. Properties cannot be used with the MERGE command.
Creating New Objects
To create a new instance of a given object class, use the class method %New() of that class. This method creates an object and returns an OREF. The following shows an example:
Set person = ##class(MyApp.Person).%New()
The %New() method accepts an argument, which is ignored by default. If present, this argument is passed to %OnNew() callback method of the class, if defined. If %OnNew() is defined, it can use the argument to initialize the newly created object in some way. For details, see Implementing Callback Methods.
If you have complex requirements that affect how you create new objects of given class, you can provide an alternative method to be used to create instances of that class. Such a method would call %New() and then would initialize properties of the object as needed. Such a method is sometimes called a factory method.
Viewing Object Contents
The WRITE command writes output of the following form for an OREF:
n@Classname
Where Classname is the name of the class, and n is an integer that indicates a specific instance of this class in memory. For example:
MYNAMESPACE>write p
8@Sample.Person
If you use the ZWRITE command with an OREF, InterSystems IRIS displays more information about the associated object.
MYNAMESPACE>zwrite p
p=<OBJECT REFERENCE>[8@Sample.Person]
+----------------- general information ---------------
| oref value: 1
| class name: Sample.Person
| %%OID: $lb("3","Sample.Person")
| reference count: 2
+----------------- attribute values ------------------
| %Concurrency = 1 <Set>
| DOB = 33589
| Name = "Clay,George O."
| SSN = "480-57-8360"
+----------------- swizzled references ---------------
| i%FavoriteColors = "" <Set>
| r%FavoriteColors = "" <Set>
| i%Home = $lb("5845 Washington Blvd","St Louis","NM",55683) <Set>
| r%Home = "" <Set>
| i%Office = $lb("3413 Elm Place","Pueblo","WI",98532) <Set>
| r%Office = "" <Set>
| i%Spouse = ""
| r%Spouse = ""
+-----------------------------------------------------
Notice that this information displays the class name, the OID, the reference count, and the current values (in memory) of properties of the object. In the section swizzled references, the items with names starting i% are instance variables. (The items with names starting r% are for internal use only.)
Introduction to Dot Syntax
With an OREF, you can use dot syntax to refer to properties and methods of the associated object. This section introduces dot syntax, which is also discussed in later sections, along with alternative ways to refer to properties and methods of objects.
The general form of dot syntax is as follows:
oref.membername
For example, to specify the value of a property for an object, you can use a statement like this:
Set oref.PropertyName = value
where oref is the OREF of the specific object, PropertyName is the name of the property that you want to set, and value is an ObjectScript expression that evaluates to the desired value. This could be a constant or could be a more complex expression.
We can use the same syntax to invoke methods of the object (instance methods). An instance method is invoked from a specific instance of a class and typically performs some action related to that instance. In the following example, we invoke the PrintPerson() method on the object whose Name property was just set:
set person=##class(Sample.Person).%New()
set person.Name="Carter,Jacob N."
do person.PrintPerson()
If the method returns a value, you can use the SET command to assign the returned value to a variable:
SET myvar=oref.MethodName()
If the method does not return a value (or if you are uninterested in the return value), use either DO or JOB:
Do oref.MethodName()
If the method accepts arguments, specify them within the parentheses.
Set value = oref.methodName(arglist)
Cascading Dot Syntax
Depending on the class definition, a property can be object-valued, meaning that its type is an object class. In such cases, you can use a chain of OREFs to refer to a property of the properties (or to a method of the property). This is known as cascading dot syntax. For example, the following syntax refers to the Street property of the HomeAddress property of a Person object:
set person.HomeAddress.Street="15 Mulberry Street"
In this example, the person variable is an OREF, and the expression person.HomeAddress is also an OREF.
When referring to a class member generally, sometimes the following informal reference is used: PackageName.ClassName.Member, for example, the Accounting.Invoice.LineItem property. This form never appears in code.
Cascading Dot Syntax with a Null OREF
When you use a chain of OREFs to refer to a property, and an intermediate object has not been set, it is often desirable to return a null string for the expression instead of receiving an <INVALID OREF> error on the intermediate object. Thus if the intermediate object has not been set (is equal to a null string), the value returned for the chained property reference is a null string.
For example, if pers is a valid instance of Sample.Person and pers.Spouse equals "", then the following statement sets the name variable to "":
set name=pers.Spouse.Name
If this behavior is not appropriate in some context, then your code must explicitly check the intermediate object reference.
Validating Objects
The %RegisteredObjectOpens in a new tab class provides a method, %ValidateObject, that validates the properties of an instance. This method returns 1 if the following criteria are true:
-
All required properties have values. To make a property required, you use the Required keyword. If a property is of type %StreamOpens in a new tab, the stream cannot be a null stream, that is, it cannot have a value for which the %IsNull() method returns 1.
-
The value of each property, if not null, is valid for the associated property definition.
For example, if a property is of type %BooleanOpens in a new tab, the value "abc" is not valid, but the values 0 and 1 are.
-
The value of each literal property, if not null, obeys all constraints defined by the property definition. The term constraint refers to any property keyword that applies a constraint on a property value, such as the MAXLEN, MAXVAL, MINVAL, VALUELIST, or PATTERN keywords. For a complete list, see Defining and Using Literal Properties.
For example, for a property of type %StringOpens in a new tab, the default value of MAXLEN is 50, which constrains the property to be no more than 50 characters in length.
-
For any object-valued property, all properties of the referenced object obey the preceding rules.
%ValidateObject() calls the validation logic for each property in turn. For more details, see Using and Overriding Property Methods.
If any property fails a validation check, then the method returns an error status.
Consider a Sample.Person class. Objects of this class include a required social security number property, SSN, that must be specified in the format NNN-NN-NNNN.
Property SSN As %String(PATTERN = "3N1""-""2N1""-""4N") [ Required ];
If you create a new Sample.Person object but do not include this property, then %ValidateObject() returns an error status indicating that the property is missing and does not match the pattern.
set person = ##class(Sample.Person).%New()
set status = person.%ValidateObject()
do $system.OBJ.DisplayError(status)
ERROR #5659: Property 'Sample.Person::SSN(9@Demo.Person,ID=)' required
ERROR #7209: Datatype value '' does not match PATTERN '3N1"-"2N1"-"4N'
> ERROR #5802: Datatype validation failed on property 'Sample.Person:SSN', with value equal to ""
If you set the property but do not the match the specification pattern, then %ValidateObject() returns only the pattern matching error status.
set person.SSN = "0000000000"
set status = person.%ValidateObject()
do $system.OBJ.DisplayError(status)
ERROR #7209: Datatype value '' does not match PATTERN '3N1"-"2N1"-"4N'
> ERROR #5802: Datatype validation failed on property 'Sample.Person:SSN', with value equal to ""
Once you set the property correctly, the object passes the %ValidateObject() check and returns a status of 1.
For a persistent object to be considered valid, its properties must also not violate any defined uniqueness constraints. %ValidateObject() does not check for uniqueness, because its checks against only the properties within the object, not against the properties of other objects saved to the database. The check for uniqueness occurs when you call %Save() to save the object.
Building on the previous example, suppose that the Sample.Person class constrains the SSN property to be unique.
Index SSNIndex On SSN [ Unique ]
If you create a new person and set the same social security number as the previous person, %ValidateObject() returns a status of 1 and $system.OBJ.DisplayError does not display an error for this status.
do person.%Save() // Save first object to database
set person2 = ##class(Sample.Person).%New()
set person2.SSN = "000-00-0000"
set status = person2.%ValidateObject()
do $system.OBJ.DisplayError(status)
Saving the object to the database fails, however, because of the uniqueness constraint placed on the SSN property.
set status = person2.%Save()
do $system.OBJ.DisplayError(status)
ERROR #5808: Key not unique: Sample.Person:SSNIndex:^Sample.PersonI("SSNIndex"," 000-00-0000")
Note that when you save a persistent object, the system automatically calls the %ValidateObject() method first. If the object fails this check, and it fails the uniqueness check, InterSystems IRIS does not save it. For more details, see Introduction to Persistent Objects.
Determining an Object Type
Given an object, the %RegisteredObjectOpens in a new tab class provides methods to determine its inheritance. This section discusses them.
%Extends()
To check if an object inherits from a specific superclass, call its %Extends() method, and pass the name of that superclass as the argument. If this method returns 1, then the instance inherits from that class. If it returns 0, the instance does not inherit from that class. For example:
MYNAMESPACE>set person=##class(Sample.Person).%New()
MYNAMESPACE>w person.%Extends("%RegisteredObject")
1
MYNAMESPACE>w person.%Extends("Sample.Person")
1
MYNAMESPACE>w person.%Extends("Sample.Employee")
0
%IsA()
To check if an object has a specific class as its primary superclass, call its %IsA() method, and pass the name of that superclass as the argument. If this method returns 1, the object does have the given class as its primary superclass.
%ClassName() and the Most Specific Type Class (MSTC)
Although an object may be an instance of more than one class, it always has a most specific type class (MSTC). A class is said to be the most specific type of an object when that object is an instance of that class and is not an instance of any subclass of that class.
For example, in the case of the GradStudent class inheriting from the Student class that inherits from the Person class, for instances created by the commands:
set MyInstance1 = ##class(MyPackage.Student).%New()
set MyInstance2 = ##class(MyPackage.GradStudent).%New()
MyInstance1 has Student as its MSTC, since it is an instance of both Person and Student, but not of GradStudent. MyInstance2 has GradStudent as its MSTC, since it is an instance of GradStudent, Student, and Person.
The following rules also apply regarding the MSTC of an object:
-
The MSTC of an object is based solely on primary inheritance.
-
A non-instantiable class cannot ever be the MSTC of an object. An object class is non-instantiable if it is abstract.
To determine the MSTC of an object, use the %ClassName() method, which is inherited from %RegisteredObjectOpens in a new tab
classmethod %ClassName(fullname As %Boolean) as %String
Where fullname is a boolean argument where 1 specifies that the method return a package name and class name and 0 (the default) specifies that the method return only the class name.
For example:
write myinstance.%ClassName(1)
(Similarly, you can use %PackageName() to get just the name of the package.)
Cloning Objects
To clone an object, call the %ConstructClone() method of that object. This method creates a new OREF.
The following Terminal session demonstrates this:
MYNAMESPACE>set person=##class(Sample.Person).%OpenId(1)
MYNAMESPACE>set NewPerson=person.%ConstructClone()
MYNAMESPACE>w
NewPerson=<OBJECT REFERENCE>[2@Sample.Person]
person=<OBJECT REFERENCE>[1@Sample.Person]
MYNAMESPACE>
Here, you can see that the NewPerson variable uses a different OREF than the original person object. NewPerson is a clone of person (or more precisely, these variables are pointers to separate but identical objects).
In contrast, consider the following Terminal session:
MYNAMESPACE>set person=##class(Sample.Person).%OpenId(1)
MYNAMESPACE>set NotNew=person
MYNAMESPACE>w
NotNew=<OBJECT REFERENCE>[1@Sample.Person]
person=<OBJECT REFERENCE>[1@Sample.Person]
Notice that here, both variables refer to the same OREF. That is, NotNew is not a clone of person.
For information on arguments to this method, see the InterSystems Class Reference for %Library.RegisteredObjectOpens in a new tab.
Referring to Properties of an Instance
To refer to a property of an instance, you can do any of the following:
-
Create an instance of the associated class, and use dot syntax to refer to the property of that instance, as described previously.
-
Within an instance method of the associated class, use relative dot syntax, as follows:
..PropName
You can use this expression with the SET command, or you can use it as part of another expression. The following shows some variations:
set value=..PropName write ..PropName
-
To access the property, where the property name is not determined until runtime, use the $PROPERTY function. If the property is multidimensional, the following arguments after the property name are used as indexes in accessing the value of the property. The signature is:
$PROPERTY (oref, propertyName, subscript1, subscript2, subscript3... )
Where oref is an OREF, propertyName evaluates to the name of a property method in the associated class. Also, subscript1, subscript2, subscript3 are values for any subscripts of the property; specify these only for a multidimensional property.
For more information, see $PROPERTY.
-
(Within an instance method) use the variable $this.
-
(Within an instance method) use instance variables.
-
Use the applicable property accessor (getter and setter) methods. See Using and Overriding Property Methods.
Calling Methods of an Instance
To call a method of an instance, you can do any of the following:
-
Create an instance of the associated class, and use dot syntax to call the method of that instance, as described previously.
-
(Within an instance method) to call another instance method of that class (which could be an inherited method), use the following expression:
..MethodName(args)
You can use this expression with the DO command. If the method returns a value, you can use SET, or you can use it as part of another expression. The following shows some variations:
do ..MethodName() set value=..MethodName(args)
-
To execute an instance method, where the method name is not determined until runtime, use the $METHOD function:
$METHOD(oref, methodname, Arg1, Arg2, Arg3, ... )
where oref is an OREF, methodname evaluates to the name of an instance method in the associated class, and Arg1, Arg2, Arg3, and so on are any arguments to that method.
For more information, see $METHOD.
-
(Within an instance method) use the variable $this.
Getting the Class Name from an Instance
To get the name of a class, use the $CLASSNAME function:
$CLASSNAME(oref)
where oref is an OREF.
For more information, see $CLASSNAME.
$this Variable (Current Instance)
The $this syntax provides a handle to the OREF of the current instance, such as for passing it to another class or for another class to refer to properties of methods of the current instance. When an instance refers to its properties or methods, the relative dot syntax is faster and thus is preferred.
$this is not case-sensitive; hence, $this, $This, $THIS, or any other variant all have the same value.
For example, suppose there is an application with an Accounting.Order class and an Accounting.Utils class. The Accounting.Order.CalcTax() method calls the Accounting.Utils.GetTaxRate() and Accounting.Utils.GetTaxableSubtotal() methods, passing city and state of the current instance to the GetTaxRate() method and passing the list of items ordered and relevant tax-related information to GetTaxableSubtotal(). CalcTax() then uses the values returned to calculate the sales tax for the order. Hence, its code is something like:
Method CalcTax() As %Numeric
{
Set TaxRate = ##class(Accounting.Utils).GetTaxRate($this)
Write "The tax rate for ",..City,", ",..State," is ",TaxRate*100,"%",!
Set TaxableSubtotal = ##class(Accounting.Utils).GetTaxableSubTotal($this)
Write "The taxable subtotal for this order is $",TaxableSubtotal,!
Set Tax = TaxableSubtotal * TaxRate
Write "The tax for this order is $",Tax,!
}
The first line of the method uses the ##Class syntax (described earlier) to invoke the other method; it passes the current object to that method using the $this syntax. The second line of the method uses the .. syntax (also described earlier) to get the values of the City and State properties. The action on the third line is similar to that on the first line.
In the Accounting.Utils, the GetTaxRate() method can then use the handle to the passed-in instance to get handles to various properties — for both getting and setting their values:
ClassMethod GetTaxRate(OrderBeingProcessed As Accounting.Order) As %Numeric
{
Set LocalCity = OrderBeingProcessed.City
Set LocalState = OrderBeingProcessed.State
// code to determine tax rate based on location and set
// the value of OrderBeingProcessed.TaxRate accordingly
Quit OrderBeingProcessed.TaxRate
}
The GetTaxableSubtotal() method also uses the handle to the instance to look at its properties and set the value of its TaxableSubtotal property.
Hence, if we invoke the CalcTax() method of MyOrder instance of the Accounting.Order class, we would see something like this:
>Do MyOrder.CalcTax() The tax rate for Cambridge, MA is 5% The taxable subtotal for this order is $79.82 The tax for this order is $3.99
i%PropertyName (Instance Variables)
This section introduces instance variables. You do not need to refer to these variables unless you override an accessor method for a property; see Using and Overriding Property Methods.
When you create an instance of any class, the system creates an instance variable for each non-calculated property of that class. The instance variable holds the value of the property. For the property PropName, the instance variable is named i%PropName, and this variable name is case-sensitive. These variables are available within any instance method of the class.
For example, if a class has the properties Name and DOB, then the instance variables i%Name and i%DOB are available within any instance method of the class.
Internally, InterSystems IRIS also uses additional instance variables with names such as r%PropName and m%PropName, but these are not supported for direct use.
Instance variables have process-private, in-memory storage allocated for them. Note that these variables are not held in the local variable symbol table and are not affected by the Kill command.