Using Primitive Types with TStrings in iOS

Problem

One of the unwelcome surprises with coding in Delphi for iOS is that you cannot store primitive types (integer, string, single, etc) in TStrings.Objects.  Usually, I am storing TObjects in the TStrings but occasionally it needs to be a primitive type (such as a score).   I would never do this for components but for programs where I know exactly what I place in the TStrings, I find nothing wrong with this and it is incredibly useful.

But with Delphi for iOS, when you try:

ListBox1.Items.AddObject('Tom', TObject(100));

It blows up.  In retrospect (and with a little digging with the debugger), it is obvious why you cannot.  With an automatic reference counting compiler, any new references to an object means that the compiler inserts calls to __ObjAddRef when you add an object to the Listbox’s list.  Since the TObject(100) is not a real object, kaboom!

Now if Delphi was a “rooted” language (i.e., even primitive types have a TObject equivalent, similar to .NET or java), this would not be a problem.  The primitive would automatically be boxed and unboxed as needed to store it inside a TObject.  However, Delphi is not a rooted language, at least not yet.   Well, the technique is so useful I was unwilling to give it up.

Solution

By using generics and class operators, I have come up with a generic class for boxing and unboxing primitive types (actually any type) which I find pretty slick.  Before I get into the details, I want to show how you would use it.  To add a primitive to a TStrings, just use the TBoxXXX cast where XXX is the primitive type name:

ListBox1.Items.AddObject('Hello', TBoxInteger(10));
ListBox1.Items.AddObject('World', TBoxString('It is a nice day'));
ListBox1.Items.AddObject('How', TBoxDouble(20.23334));

To unbox back to the primitive type, it is a little more complicated.  First, you cast your TObject back to its TBoxXXX type and then you can cast it either explicitly or implicitly back to the primitive type:

var
  i: Integer;
begin
  i := TBoxInteger(ListBox1.Items.Objects[0]);
  ShowMessage(i.ToString);
end;

Easy as can be! :-)  The one wrinkle is that the IndexOfObject method will no longer work because the method compares the TObject to the TBoxInteger object and not its value.  To handle that case, I have added an IndexOf class method:

ShowMessage(TBoxInteger.IndexOf(ListBox1.Items, 100).ToString);

Code

So how does the code work?  The whole trick is to define a generic boxing class.  This class contains a private field containing the primitive value.  It then defines implicit and explicit conversions from the primitive to the boxing object and back.  It adds the previously mentioned IndexOf method and defines the Equal class operator to make it work.  The full declaration is thus:

TRSBoxPrimitive<T> = class(TObject)
{ Purpose: To provide a simple class for boxing and unboxing a primitive type
Note: this class is only for use by AUTOREFCOUNT compilers }
private
  { private declarations }
  FValue: T;
protected
  { protected declarations }
public
  { public declarations }
  constructor Create( const V: T );
  class operator Equal(a,b: TRSBoxPrimitive<T>): Boolean;
  class operator NotEqual(a,b: TRSBoxPrimitive<T>): Boolean;
  class function IndexOf( const Strings: TStrings; a: T ): Integer;
  class operator Implicit(a: T): TRSBoxPrimitive<T>; 
  class operator Implicit(a: TRSBoxPrimitive<T>): T;
  class operator Explicit(a: T): TRSBoxPrimitive<T>; 
  class operator Explicit(a: TRSBoxPrimitive<T>): T; 
end; { TRSBoxPrimitive<T> }

When you type TBoxInteger(100), the Explicit(a: T): TRSBoxPrimitive<T> method is called:

class operator TRSBoxPrimitive<T>.Explicit(a: T): TRSBoxPrimitive<T>;
begin
  result := TRSBoxPrimitive<T>.Create(a);
end;

The line assigning the boxed value back to an integer calls the Implicit(a: TRSBoxPrimitive<T>): T method:

class operator TRSBoxPrimitive<T>.Implicit(a: TRSBoxPrimitive<T>): T;
begin
  result := a.FValue;
end;

The final code creates primitive boxing types from the generic type:

TBoxInteger = TRSBoxPrimitive<Integer>;
TBoxString = TRSBoxPrimitive<String>;
TBoxDouble = TRSBoxPrimitive<Double>;
...

The code takes advantage of the fact that the TBoxXXX classes are reference counted and will be automatically destroyed.  Otherwise, this code would leak like a sieve!  A cool bonus with this code is that the boxing allows primitive types that would not usually fit in the size of TObject.

Hopefully, this code is as useful to you as it is to me.  The full code is available here for you to use as you see fit (but please keep the attribution and let me know if you improve it)

Happy CodeSmithing!