Nullable Types

Back to 2008, Allen Bauer wrote A “Nullable” Post. Although this interesting stuff has not been introduced in Delphi RTL yet, it has been in the Spring4D family for a while.

So, what are nullable types and when we should use them? Let’s get the answer from a simple example:

// uses Spring;
TPerson = class
private
  fBirthDate: Nullable<TDateTime>;
public
  property BirthDate: Nullable<TDateTime> read fBirthDate write fBirthDate;
  property Age: Nullable<Integer> read GetAge;  // Calculated Property
end;

The BirthDate of a person is optional (not required) therefore we may not be able to calculate his(her) age. Nullable<T> type perfectly fit this kind of cases. Otherwise, we have to use some special values (magic number) to represent this nullabe state which is really a bad smell. It also makes it easier to map database data types to Delphi as most data type may be null.

OK, now let’s put the mini Nullable<T> on table:

  Nullable<T> = packed record
  private
    fValue: T;
    fHasValue: string;
  public
    constructor Create(const value: T); overload;
    constructor Create(const value: Variant); overload;

    function GetValueOrDefault: T; overload;
    function GetValueOrDefault(const defaultValue: T): T; overload;

    function Equals(const other: Nullable<T>): Boolean;

    property HasValue: Boolean read GetHasValue;
    property Value: T read GetValue;
  end;

Nullable<T> is a record type wrapper which allows you use it without any initialization. T is the underlying type of the nullable type. T can be any value type, e.g. Integer, TDateTime, Boolean, String or even another record type. The following example demonstrates how to use a nullable integer:

procedure TestNullableUsage;
var
  age: Nullable<Integer>;
  value: Integer;
begin
  Assert(not age.HasValue);

  // An EInvalidOperationException exception will be raised 
  // when accessing the Value property of a null variable
  try
    age.Value; 
  except on e: Exception do
    Assert(e is EInvalidOperationException);
  end;

  Assert(age.GetValueOrDefault = Default(Integer));
  Assert(age.GetValueOrDefault(30) = 30);

  // Implicit Conversions between Nullable<T> and T
  age := 30;
  value := age;

  // Equality Operators
  Assert(age = value);
  Assert(age <> 20);

  // Assign nil to the nullable value to make it null
  age := nil;
  Assert(not age.HasValue);
end;

Keynotes:

1. Nullable<T> is designed as immutable.

Nullable<T> itself doesn’t provide any member to change its internal state or value. When you need a writable property, you must declare a setter, just like TPerson.BirthDate, otherwise, the property is read-only.

2. Compatible with Variant

When you pass a Variant value, whose type is either varNull or varEmpty, to a nullable variable, the latter will be marked as null.

3. Equality Operator

function Nullable<T>.Equals(const other: Nullable<T>): Boolean;
begin
  if HasValue and other.HasValue then
    Result := TEqualityComparer<T>.Default.Equals(Value, other.Value)
  else
    Result := HasValue = other.HasValue;
end;

4. Tricky implementation of HasValue

Since we cannot define the default constructor for record types,  how to ensure that the HasValue proerty always returns false when the value has not been initialized? It is really not a good idea to use a Boolean flag in a record type.

procedure TestNullableHasValue;
var
  age: Nullable<Integer>; // Lives in stack
begin
  Assert(not age.HasValue);
end;

As Hallvard Vassbotn had reminded us in the comments of the post, the compiler will automatically initialize a record once when it contains a managed type field (string, interface, etc.). We use the string flag in Spring4D.

constructor Nullable<T>.Create(const value: T);
begin
  fValue := value;
  fHasValue := CHasValueFlag; // '@'
end;

function Nullable<T>.GetHasValue: Boolean;
begin
  Result := fHasValue <> '';
end;

 

Note: This post is a part of Spring4D in Action Series.

http://www.spring4d.org

Copyright 2012 (c) Baoquan Zuo

3 comments

  1. An interface alone doesn’t have an advantage over string, only if you implement it using that “fake instance” as Allen described it will be faster – but also a bit more complicated to understand – so maybe they didn’t want to scare all Spring users ;-)

    Olaf

Leave a Reply

Your email address will not be published. Required fields are marked *

*


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>