Well, I'm neither part of the ObjectScript nor the Objects developer team, hence I can't answer the "why" part of your question but fact is, timing mesuremenst show a significat higher speeds for the literal versions:
 

ClassMethod DynObject()
{
	while $zh#1 {} set t1=$zh for i=1:1:1E6 { if ##class(%DynamicArray).%New()  } set t1=$zh-t1
	while $zh#1 {} set t2=$zh for i=1:1:1E6 { if ##class(%DynamicObject).%New() } set t2=$zh-t2
	while $zh#1 {} set t3=$zh for i=1:1:1E6 { if [] } set t3=$zh-t3
	while $zh#1 {} set t4=$zh for i=1:1:1E6 { if {} } set t4=$zh-t4
	
	write "Times for :    Class  Literal     Diff",!
	write "DynArray  :", $j(t1,9,3), $j(t3,9,3), $j(t1/t3-1*100,9,2),"%",!
	write "DynObject :", $j(t2,9,3), $j(t4,9,3), $j(t2/t4-1*100,9,2),"%",!
}

The output will depend on
- IRIS/Cache version in use and
- on the underlying hardware
My values are

USER>d ##class(DC.Times).DynObject()
Times for :    Class  Literal     Diff
DynArray  :    0.665    0.401    65.90%
DynObject :    0.649    0.401    61.87%

Maybe someone else or the WRC has an explanation...

My guess is, because abstract classes (%CSP.Page is an abstract class) can't be instantiated, your subclass lacks the generator methos for property initialisation.

But there is a simple solution:

Class Your.Page Extends (%RegisteredObject, %CSP.Page)
{
Property Name As %String [ InitialExpression = "Joe" ];
}

Well, the world is right again

set page=##class(Your.Page).%New()
write page.Name --> Joe

Try it this way...

Class DC.OldStuff Extends %Persistent [ StorageStrategy = NewStorage1 ]
{

Property Rec As %Integer [ Identity ];
Property Name As %String;
Property City As %String;
Property Phone As %String;
Storage NewStorage1
{
<SQLMap name="Map1">
<Data name="City">
<RetrievalCode>s {*}=$zel(^myGlo("R",{L2}),2)</RetrievalCode>
</Data>
<Data name="Name">
<RetrievalCode>s {*}=$zel(^myGlo("R",{L2}),1)</RetrievalCode>
</Data>
<Data name="Phone">
<RetrievalCode>s {*}=$zel(^myGlo("R",{L2}),3)</RetrievalCode>
</Data>
<Global>^myGlo</Global>
<Subscript name="1">
<Expression>"R"</Expression>
</Subscript>
<Subscript name="2">
<Expression>{Rec}</Expression>
</Subscript>
<Type>data</Type>
</SQLMap>
<StreamLocation>^DC.OldS</StreamLocation>
<Type>%CacheSQLStorage</Type>
}

}

A short test shows, it works

USER>k ^myGlo
USER>s ^myGlo("R",1)=$zlp("John,Boston,11-22-33")
USER>s ^myGlo("R",5)=$zlp("Laura,New York,333-444-555")
USER>s ^myGlo("R",7)=$zlp("Paul,Chicago,556-666-777")
USER>d $system.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
USER>>select * from DC.OldStuff
3.      select * from DC.OldStuff

ID      City    Name    Phone   Rec
1       Boston  John    11-22-33        1
5       New York        Laura   333-444-555     5
7       Chicago Paul    556-666-777     7
3 Rows(s) Affected
statement prepare time(s)/globals/lines/disk: 0.0003s/5/159/0ms
          execute time(s)/globals/lines/disk: 0.0003s/13/1136/0ms
                          cached query class: %sqlcq.USER.cls43
---------------------------------------------------------------------------
USER>>quit

or as objects

USER>s obj=##class(DC.OldStuff).%OpenId(7)

USER>w obj.Name,!,obj.City,!,obj.Phone
Paul
Chicago
556-666-777
USER>

First step: create your own method, for example

Class DC.Unix [ Abstract ]
{
/// Convert Posix time into Timestamp
/// 
/// posix: posix time
///    df: date format
///    tf: time format
///    dp: decimal places
ClassMethod PosixToTimeStamp(posix, df = 3, tf = 1, dp = 0)
{
	set posix=posix-1152921504606846976/1E6
	quit $zdt(posix\86400+47117_","_(posix#86400),df,tf,dp)
}
}

Next step, if you need more speed, instead of the parameters <df> and <tf> use constants and remove <dp>. The very last step: shorten the method name from PosixToTimeStamp() to P2TS()

This way I got the (nearly) the same times as with (%Library.PosixTime).LogicalToTimeStamp, but without the need for string manipulation 

ICINDY:USER>k

ICINDY:USER>set posix = 1154669852181849976

ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(%Library.PosixTime).LogicalToTimeStamp(posix) } w $zh-t
.902538
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(%Library.PosixTime).LogicalToTimeStamp(posix) } w $zh-t
.90609
ICINDY:USER>

ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).PosixToTimeStamp(posix) } w $zh-t
.934834
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).PosixToTimeStamp(posix) } w $zh-t
.944418
ICINDY:USER>

ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).P2TS(posix) } w $zh-t
.913609
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).P2TS(posix) } w $zh-t
.905303
ICINDY:USER>

ICINDY:USER>w $zv
IRIS for UNIX (Ubuntu Server LTS for x86-64) 2021.2 (Build 649U) Thu Jan 20 2022 08:49:51 EST
ICINDY:USER>w x
2025-05-27 12:06:15
ICINDY:USER>

An easy and simple (assuming standard storage strategy) way to get those informations (the slot numbers, where a property is stored) could be achieved with a simple classmethod

Class DC.PropInfo [ Abstract ]
{

/// Info about properties of a class:
/// - list of all properties stored in a list
/// - slot number for a given property
/// 
/// 1) add this class to your class definition
///    class some.class extends (%Persistent, DC.StorageInfo)  or
///    class some.class extends (%SerialClass, DC.StorageInfo)
///           
/// 2) then use it as follows
///    write ##class(some.class).PropInfo()      --> list of property names
///    write ##class(some.class).PropInfo("Age") --> slot number for the Age property
/// 
/// write ##class(
ClassMethod PropInfo(name = "") As %String [ CodeMode = objectgenerator ]
{
	set sto=%compiledclass.Storages, prp=0, list=""
	
	if sto.Count()=1 {
		set dat=sto.GetAt(1).Data
		for i=1:1:dat.Count() if dat.GetAt(i).Structure="listnode" set prp=dat.GetAt(i) quit
	
		if prp {
			if %compiledclass.ClassType="serial" { set list="", i=1 } else { set list=$lb(""), i=2 }
			for i=i:1:prp.Values.Count() set list=list_$lb(prp.Values.GetAt(i).Value)
		}
		
		do %code.WriteLine(" if name="""" quit """_$lts(list)_"""")
		do %code.WriteLine(" quit $lf($lfs("""_$lts(list)_"""),name)")
	}
	
	if list="" write !,"*** No properties found! ***"
	quit $$$OK
}

}

Two test classes

Class DC.TestPerson Extends (%Persistent, DC.PropInfo)
{
Property Name As %String;
Property Addr As DC.Address;
Property Salary As %Numeric;
Property Expertise As list Of %String;
}


Class DC.Address Extends (%SerialObject,DC.PropInfo)
{
Property Street As %String;
Property City As %String;
}

Now you can do things like:

write ##class(DC.TestPerson).PropInfo()         --> ,Name,Addr,Salary,Expertise
write ##class(DC.TestPerson).PropInfo("Salary") --> 4
// you can use the slot number for direct data access:
write $list(^DC.TestPersonD(id),slotnumber)  gives you the same value as
write ##class(DC.TestPerson).SalaryGetStored(id)

// the same game for serial classes
write ##class(DC.Address).PropInfo()       --> Street,City
write ##class(DC.Address).PropInfo("City") --> 2

// in case you have an instance
set pers=##class(DC.TestPerson).%OpenId(id)
write pers.PropInfo()      --> ,Name,Addr,Salary,Expertise
write pers.Addr.PropInfo() --> Street,City

// etc.

First, measuring execution times on modern operating systems where multiple processes run in parallel (on multiple CPUs) is challenging. The following demo application assigns a value to a variable in four different ways:
– in a single method
– in two methods, both in the same class
– in two methods where one method code is in an inherited class, and
– in two methods where one method is in a different class

As expected, the first is the fastest (keyword: loop unrolling) and the last is the slowest, the other two take about the same time.

Class DC.Times Extends (%RegisteredObject, TimesAbstract)
{
ClassMethod ShowTimes()
{
	while $zh#1 {} set t1=$zh for i=1:1:1E6 { do ..Complete() } set t1=$zh-t1
	while $zh#1 {} set t2=$zh for i=1:1:1E6 { do ..OneClass() } set t2=$zh-t2
	while $zh#1 {} set t3=$zh for i=1:1:1E6 { do ..InhClass() } set t3=$zh-t3
	while $zh#1 {} set t4=$zh for i=1:1:1E6 { do ..TwoClass() } set t4=$zh-t4
	write $j(t1,9,5), $j(t2,9,5), $j(t3,9,5), $j(t4,9,5),!
}
/// The complete application is carried out in one method
ClassMethod Complete()
{
	set x=12345
	set y=12345
}
/// The entire application is done in the same class, but with different methods
/// Both methods are local (OneClass + LocTask)
ClassMethod OneClass()
{
	set x=..LocTask()
	set y=..LocTask()
}
/// The entire application is done in the same class, but with different methods
/// One method is local (InhClass) the other is inherited (InhTask)
ClassMethod InhClass()
{
	set x=..InhTask()
	set y=..InhTask()
}
/// The entire application uses two methods in two different classes
ClassMethod TwoClass()
{
	set x=##class(DC.Times2).ExtTask()
	set y=##class(DC.Times2).ExtTask()
}
/// As an "application" we simply return a constant value
ClassMethod LocTask(val)
{
	quit 12345
}
}

Class DC.Times2 Extends %RegisteredObject
{
/// As an "application" we simply return a constant value
ClassMethod ExtTask(val)
{
	quit 12345
}
}

Class DC.TimesAbstract [ Abstract ]
{
/// As an "application" we simply return a constant value
ClassMethod InhTask(val)
{
	quit 12345
}
}

Some time values

USER>

USER>f i=1:1:3 d ##class(DC.Times).ShowTimes()
  0.10833  0.19660  0.19649  0.22001
  0.10837  0.19657  0.19608  0.22000
  0.10826  0.19661  0.19603  0.21992

USER>

USER>f i=1:1:3 d ##class(DC.Times).ShowTimes()
  0.10998  0.19711  0.19643  0.22006
  0.10830  0.19657  0.19624  0.22013
  0.10822  0.19684  0.19628  0.22139

USER>

USER>w $zv
IRIS for UNIX (Ubuntu Server 22.04 LTS for x86-64) 2025.1 (Build 225_1U) Fri May 16 2025 12:18:04 EDT
USER>

Second, the choice of development method (use of include files, class inheritance, multiple classes, a large method in an even larger class, etc.) depends on other factors such as maintainability, runtime priority, etc.

If you have set the "handling of undefined" settings to 2 then in either case (you set x to a nullstring OR x is undefined) in statements like

if $d(@x@(whatever))
set d=$d(@x@(whatever))

you get a <SYNTAX> error, because nullstring (the value of x) is not a valid name for a local or global variable.

In a terminal session issue the following commands and check each output with mine:

kill         // kill all local variables
write x=""   // the output is 1 (because of your setting)
if $d(@x)    // the output is a <SYNTAX> because "" is not a valid name
set d=$d(@x) // the output is a <SYNATX> because "" not a valid name

If your output is different from the above, than please contact the WRC
My $ZV is IRIS for UNIX (Ubuntu Server LTS for x86-64) 2021.2 (Build 649U) Thu Jan 20 2022 08:49:51 EST

I keep on nagging...
I expected to see the environment of your IF-line: is it in a block structure or not. Which variables are initialized, etc. But you always give me the same expressiveness information: just the IF-Statement.

Nevertheless, I think, I know, why you get the <SYNTAX> error:

- under "default conditions", executing the following lines gives you an <UNDEFINED> error

  kill
  set a=1,b=2   // x is not defined
  if $d(@x@(a,b))

- but the very same lines gives you a <SYNTAX> error if the setting, how undefined variables should be handled is set to 1 or 2 (I suppose, in your case it is set to 2) because x as nullstring is not a valid variable or global name.


To see the setting in question, go to:
ManagementPortal-->SystemAdministration-->AdditionalSettings-->Compatibility and look for the setting "Undefined". I'm pretty sure, there is a 2 (or maybe 1). The default setting is 0.

Hint: the above mentioned setting is a per job setting, you must start a new job to have the new setting in effect

I think you are leaving out some important information: you have a terminal session, but you are calling either a routine (.mac) or a (class) method, since a standard terminal session does not accept multi-lines (as you wrote above).
And when you call a routine or method, it would be nice to see that function/method. By the way, for indirection to work, you need variables with global visibility. So I'm assuming you're mixing some existing variables from your terminal session with variables from your routine/method.

It's not a permission issue!
A simple check tells you:

write ##class(%SYS.Namespace).GetRoutineDest(,"%Test") --> ^/opt/isc/icindy/mgr/irislib/

a routine named %Test will bes stored in the "irislib" database.
Now, if you take a look at the irislib database, you will see, it's a READONLY database, hence you get the <PROTECT> error. A readonly means read only, even for the Superuser!

If you absolutely need to name that routine with %-something (except %Z* and %z*) than:
- 1) remove the readonly flag from the database in question
  2) save your routine
  3) turn on the readonly flag
  4) for every change in that routine repeat the abowe game: 1), 2) and 3)
- and don't forget to make a copy of your %-something routine
  because each IRIS update REMOVES ALL %-routines except those %Z* and %z*