It's really a matter of taste.

If you don't like the long list of params (especially with lousy documented methods) you can

#1) use 1 single param and pass a local array byRef .  and decode it yourself eg:

par("IPadrr")="127.0.0.1")
par("Port")=1972
par(Namespace")="SAMPLES"

.....

do  ##class(my.par).method(.par)

or

#2) use the traditional style you may know from Caché I/O Device Guide  having also just 1 parameter

do  ##class(my.pieces).method("/SERVER=127.0.0.1:/PORT=1972:/NAMESPACE=SAMPLES")

I personally prefer #2) as it gives you an embedded doc on your intentions.

#1) all documentation on XML is here

It covers everything to output Objects to XML.

#2) to fill  this in-between object you may use the SQL Table represented by an object class.
 as simple INSERT  INTO  my.xmltable select .........

You just have to take care that the object class also extends %XML.Adapter .
That makes it ready for XML Export.  All details described in docs  

Dear Martin,

I have a rather clear vision  where your $example comes from ($zzg, $zza, ...)  wink
My personal suggestion is to move everything you maintain today in %ZZLANG?00 routines into clean Macro definitions (.inc)

The history of %LANG* code goes back to times when migration from other language dialects (MSM, DTM, DSM, VISOS, ..) happened
and developers were writing just in  .INT routines. That's far back in the late 80ies.  The availability of MACRO code (also ages back) made it almost obsolete. The feature was never eliminated by considerations of backward compatibility. 

I personally would never allow any developer to touch %SYS. 
And wouldn't accept any code using $zz* , zz* stuff or $zu().
There are much cleaner ways to achieve the same result without compromising the core. 

I may misunderstand your intentions but

when I use "ResultSet.%Get("Collection")", all I get is a list of the primary keys of the objects

now you all you miss for each PrimaryKey is 

Set collB=##class(ICT.Experiments.B).%OpenId(primaryKey) 

or

Set collC=##class(ICT.Experiments.C).%OpenId(primaryKey)

and the object is yours.

With your class definition,  PrimaryKey is the Idkey of the Object.

the structure of $LB() is rather simply a binary string

-----------element--------------
TotalLength = 1, 3, 7 bytes depending on size    *corrected*
Type = 1 byte  (check in JSON converter for codes, or just check with ZZDUMP)
Content : size = TotalLength-1-size of length field
-----------element--------------
TotalLength = 1, 3, 7 bytes depending on size    *corrected*
Type = 1 byte  (check in JSON converter for codes, or just check with ZZDUMP)
Content : size= TotalLength-1-size of length field
-----------element--------------

...

Therefore concatenation of $lb) is so easy

Sorry,  I had more changes. 
My approach in details

Class DC.String Extends %String  {
ClassMethod Get() As %String [ CodeMode = objectgenerator ]
{    do %code.WriteLine($c(9) _ "Quit $g(^Test.String, 0)")
    quit $$$OK  }
ClassMethod Set(%val As %String) [ CodeMode = objectgenerator ] {
    do %code.WriteLine($c(9) _ "Set ^Test.String = %val")
    do %code.WriteLine($c(9) _ "do zTest()")
    quit $$$OK }

/// this generates Method propTest()
/// but satisfies the Compiler for this class
ClassMethod Test() { quit  ;; from Data definition }
}

compiles this routine:

 ;DC.String.1
 ;(C)InterSystems, generated for class DC.String. Do NOT edit. 10/04/2019 12:00:42PM
 ;;42345370;DC.String
 ;
zGet() public  Quit $g(^Test.String, 0) }
zSet(%val) public Set ^Test.String = %val
      do zTest() }
zTest() public  quit  ;; from Data definition }

and the  using class

Class DC.StringTest Extends %RegisteredObject  {
Property prop As DC.String;
Method Test()
 b   quit ;; from using Class }
}

compiled as 

 ;DC.StringTest.1
 ;(C)InterSystems, generated for class DC.StringTest. Do NOT edit. 10/04/2019 12:02:35PM
 ;;49614632;DC.StringTest
 ;
%NormalizeObject() public {
If '$system.CLS.GetModified() Quit 1
If m%prop Set:i%prop'="" i%prop=(..propNormalize(i%prop))
Quit 1 }
%ValidateObject(force=0,checkserial=1) public {
set sc=1
If '$system.CLS.GetModified() Quit sc
If m%prop Set iv=..prop If iv'="" Set rc=(..propIsValid(iv)) If ('rc) Set sc=$$EmbedErr^%occSystem(sc,rc,5802,"prop",iv)
Quit sc }
zTest() public {
 b   quit ;; from using Class
}
zpropGet() public {
Quit $g(^Test.String, 0) }
zpropSet(%val) public {
Set ^Test.String = %val
do zTest() }
zpropTest() public {
 quit  ;; from Data definition
}

and the test

 SAMPLES>s obj=##class(DC.StringTest).%New()
SAMPLES>set obj.prop=123
 b   quit ;; from using Class
 ^
<BREAK>zTest+1^DC.StringTest.1
SAMPLES 3d1>

Well, the code generator can be tricky.
It's not one of my favorites. But sometimes you have no choice.

OK.
StorageTo../..ToStorage only works in persistent classes where you move content from/to globals.
No chance without storage. 

It doesn't get called in Registered Classes and not when the object wasn't saved.
see my test with %Persistent

set obj=##class(Test.String).%New()
write 
obj.prop    ;nothing loaded yet

set obj.prop=77 write obj.prop   ; unchanged as neither stored nor loaded
77
do obj.%Save(),obj,%Reload() ; force reload to trigger StorageToLogical
write obj.prop  ; and here we go
prop


This proves that there is limited use of the approach.

Ken Olson , the founder of DEC (Digital Equipment Corp.) was  famous for its statement:

  • "I can't imagine any good reason for someone to have a computer at his home"

 Similar in the late 1950ies IBM estimated the worldwide market for computers of 15..30 systems in total.

So lack of phantasy what could be in the future is quite common also for very successful people and companies.