Author: Barry K21557
[This blog post is by Allen Bauer, but during the blog engine migration was attributed to the wrong author.]
Solving problems can be fun and challenging. What is really challenging is solving a problem using only the tools at hand. During the development of Delphi 2009, there were several language features that were dropped in favor of generics and anonymous methods. One such feature was what we called “managed records.” This allowed for some very interesting things where you could now implement a constructor, destructor and several assignment class operators on a record that would be automatically called when the record came into scope, went out of scope, and was assigned to something or something was assigned to it (like another instance of itself). Early in the development cycle, I cobbled together a generic record type that would implement a Nullable
type
Nullable= record
private
FValue: T;
FHasValue: Boolean;
function GetValue: T;
public
constructor Create(AValue: T);
function GetValueOrDefault: T; overload;
function GetValueOrDefault(Default: T): T; overload;
property HasValue: Boolean read FHasValue;
property Value: T read GetValue;
class operator Implicit(Value: Nullable): T;
class operator Implicit(Value: T): Nullable;
class operator Explicit(Value: Nullable): T;
end;
However, it suffered from one major flaw that managed records would have cleanly solved. Mainly, that just declaring a variable of type Nullable
During the 1980’s I enjoyed watching a television show called “MacGyver.” The premise was that the main character used his own clever ingenuity to get out of a jam using only the tools and items he had at his disposal at the moment. As sort of a pop-cultural icon, this MacGyver character would concoct some of the most unlikely solutions to get away from the “bad guys” each week. The jokes and hyperbole surrounding this was fun, “MacGyver saves the day by shutting down the critical nuclear reactor core with only a paper clip and plastic bandage!” Even though a lot of what this character did defied common-sense and logic, the premise was that using a little ingenuity, you can solve seemingly impossible problems.
In true MacGyver fashion I figured I’d dust off my Nullable
Let’s go back to the key problem with the above type, all we really care about is that when a variable of type Nullable
In the Nullable
type
Nullable= record
private
FValue: T;
FHasValue: IInterface;
...
function GetHasValue: Boolean;
public
constructor Create(AValue: T);
...
property HasValue: Boolean read GetHasValue;
...
end;
So I’ve changed the FHasValue field to be an IInterface and changed the HasValue property to now call a getter method since HasValue is still a Boolean and FHasValue is an interface. All GetHasValue does is to return whether or not FHasValue is nil. Then we change the constructor to assign something to FHasValue, namely just create an instance of TInterfacedObject and assign the implemented IInterface to the field.
constructor Nullable.Create(const AValue: T);
begin
FValue := AValue;
FHasValue := TInterfacedObject.Create;
end;
Now when you assign one Nullable
But can this be made a little more efficient? After all, if we’re “MacGyver-ing” this up, might as well go all the way. If we pull another trick out of MacGyver’s bag-of-tricks (say a “plastic bandage”), we can eliminate the worry about leaking the object and get a slight performance gain to boot. This trick is used down inside Generics.Defaults.pas for creating a singleton interface. All we really care about is that FHasValue is properly initialized to nil and when we assign to the Nullable
Down in the implementation section we declare the following:
function NopAddref(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopRelease(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopQueryInterface(inst: Pointer; const IID: TGUID; out Obj): HResult; stdcall;
begin
Result := E_NOINTERFACE;
end;
const
FlagInterfaceVTable: array[0..2] of Pointer =
(
@NopQueryInterface,
@NopAddref,
@NopRelease
);
FlagInterfaceInstance: Pointer = @FlagInterfaceVTable;
And in the Nullable
constructor Nullable.Create(const AValue: T);
begin
FValue := AValue;
FHasValue := IInterface(@FlagInterfaceInstance);
end;
And now all instances of Nullable
unit Foo;
interface
uses Generics.Defaults, SysUtils;
type
Nullable= record
private
FValue: T;
FHasValue: IInterface;
function GetValue: T;
function GetHasValue: Boolean;
public
constructor Create(AValue: T);
function GetValueOrDefault: T; overload;
function GetValueOrDefault(Default: T): T; overload;
property HasValue: Boolean read GetHasValue;
property Value: T read GetValue;
class operator NotEqual(ALeft, ARight: Nullable): Boolean;
class operator Equal(ALeft, ARight: Nullable): Boolean;
class operator Implicit(Value: Nullable): T;
class operator Implicit(Value: T): Nullable;
class operator Explicit(Value: Nullable): T;
end;
procedure SetFlagInterface(var Intf: IInterface);
implementation
function NopAddref(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopRelease(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopQueryInterface(inst: Pointer; const IID: TGUID; out Obj): HResult; stdcall;
begin
Result := E_NOINTERFACE;
end;
const
FlagInterfaceVTable: array[0..2] of Pointer =
(
@NopQueryInterface,
@NopAddref,
@NopRelease
);
FlagInterfaceInstance: Pointer = @FlagInterfaceVTable;
procedure SetFlatInterface(var Intf: IInterface);
begin
Intf := IInterface(@FlagInterfaceInstance);
end;
{ Nullable}
constructor Nullable.Create(AValue: T);
begin
FValue := AValue;
SetFlagInterface(FHasValue);
end;
class operator Nullable.Equal(ALeft, ARight: Nullable ): Boolean;
var
Comparer: IEqualityComparer;
begin
if ALeft.HasValue and ARight.HasValue then
begin
Comparer := TEqualityComparer.Default;
Result := Comparer.Equals(ALeft.Value, ARight.Value);
end else
Result := ALeft.HasValue = ARight.HasValue;
end;
class operator Nullable.Explicit(Value: Nullable ): T;
begin
Result := Value.Value;
end;
function Nullable.GetHasValue: Boolean;
begin
Result := FHasValue <> nil;
end;
function Nullable.GetValue: T;
begin
if not HasValue then
raise Exception.Create('Invalid operation, Nullable type has no value');
Result := FValue;
end;
function Nullable.GetValueOrDefault: T;
begin
if HasValue then
Result := FValue
else
Result := Default(T);
end;
function Nullable.GetValueOrDefault(Default: T): T;
begin
if not HasValue then
Result := Default
else
Result := FValue;
end;
class operator Nullable.Implicit(Value: Nullable ): T;
begin
Result := Value.Value;
end;
class operator Nullable.Implicit(Value: T): Nullable ;
begin
Result := Nullable.Create(Value);
end;
class operator Nullable.NotEqual(const ALeft, ARight: Nullable ): Boolean;
var
Comparer: IEqualityComparer;
begin
if ALeft.HasValue and ARight.HasValue then
begin
Comparer := TEqualityComparer.Default;
Result := not Comparer.Equals(ALeft.Value, ARight.Value);
end else
Result := ALeft.HasValue <> ARight.HasValue;
end;
end.
Here’s a little test that shows how it works:
procedure TestNullable;
var
NullInt: Nullable;
NullInt2: Nullable;
I: Integer;
begin
try
I := NullInt; // Exception raised here because NullInt was never assigned a value;
except
Writeln('Success');
end;
if not NullInt.HasValue then // Check if the FHasValue field is actually nil
NullInt := 10; // Exercise an Implicit operator
NullInt2 := NullInt; // Non-Null Nullable assigned to a Null Nullable makes it non-Null
if NullInt2 = 10 then // Exercise the Equal() class operator and the Implicit operator
Writeln('Success');
end;
begin
TestNullable;
end.
Now, in true “Raymond Chen” fashion, I’d like to relieve many of you from the compulsive need to post the following comments :-).
Pre-emptive snarky comments:
If you would just implement non-reference counted interfaces like any “modern” language (aka, C#, Java, etc..), you wouldn’t have to resort to those ugly hacks to fool the compiler.
Ok, so why didn’t you just implement this with the “?” syntax like C# and build it in the first place?
Reduce development time and get to market faster with RAD Studio, Delphi, or C++Builder.
Design. Code. Compile. Deploy.
Free Delphi Community Edition Free C++Builder Community Edition







