Whenever we launch the Delphi IDE, all IDE packages, design time packages and third-party add-ins will be loaded, no matter whether we use them or not. It often takes about 12 seconds on my machine while only 3 seconds for Visual Studio. This is the charm of lazy-loading which defers the creation of a resource (or an expensive operation) until the first time it is needed.
It’s very common to see the following lazy-initialization implementation in VCL framework:
class function TEncoding.GetUTF8: TEncoding; var LEncoding: TEncoding; begin if FUTF8Encoding = nil then begin LEncoding := TUTF8Encoding.Create; if InterlockedCompareExchangePointer(Pointer(FUTF8Encoding), LEncoding, nil) <> nil then LEncoding.Free; end; Result := FUTF8Encoding; end;
This is an optimistic thread-safe approach. At first, we check whether a variable (FUTF8Encoding) has been assigned at that point. If the answer is yes, its value will be just returned. Otherwise, we need to create a local instance and then do an atomic compare-and-exchange operation. The local instance will be exchanged with the variable only if the execution thread wins the race, otherwise the local one must be destroyed to avoid memory leak.
Although the above logic is not so complicated, we still have to care about “How-To-Do”, instead of “What-To-Do”. So we introduced the static class TLazyInitializer in the Spring namespace to make our life easier.
TLazyInitializer = record public class function EnsureInitialized<T: class, constructor>(var target: T): T; overload; static; class function EnsureInitialized<T>(var target: T; const factoryMethod: TFunc<T>): T; overload; static; end;
Since the TUTF8Encoding type is a class which contains a default constructor, it’s easier to refactor the example code by using the first overload:
class function TEncoding.GetUTF8: TEncoding; begin Result := TLazyInitializer.EnsureInitialized<TUTF8Encoding>(FUTF8Encoding); end;
Cool? : ) Alternatively, we are able to customize the construction by using a factory method:
class function TEncoding.GetUTF8: TEncoding; begin Result := TLazyInitializer.EnsureInitialized<TUTF8Encoding>(FUTF8Encoding, function: TUTF8Encoding begin Result := TUTF8Encoding.Create; end ); end;
Furthermore, the Spring namespace provides high level lazy-initialization support by Lazy<T> structure which is very useful in object models.
Lazy<T> = record private fLazy: ILazy<T>; public constructor Create(const valueFactory: TFunc<T>); constructor CreateFrom(const value: T); property Value: T read GetValue; property IsValueCreated: Boolean read GetIsValueCreated; class operator Implicit(const lazy: Lazy<T>): T; class operator Implicit(const value: T): Lazy<T>; end;
The Lazy<T> type is actually a wrapper of the ILazy<T> interface. It should be constructed by either a factory method (TFunc<T>) or a specified value.
Saying we are developing a client notebook application. A note has a title, a category and a html text content which may be huge. It’s a good choice to make the ORM framework load all note objects but leaving their content as unloaded. It’s also possible to lazily load an association (e.g. Category) by applying Lazy<T> to the navigation property.
TNote = class(TDomainObject) private fTitle: string; fCategory: Lazy<TCategory>; fContent: Lazy<string>; public property Title: string read fTitle write SetTitle; property Category: Lazy<TCategory> read fCategory write SetCategory; property Content: Lazy<string> read fContent write SetContent; end; procedure TNote.SetContent(const value: Lazy<string>); begin fContent := value; NotifyPropertyChanged('Content'); end;
This approach costs less than using virtual property getters & setters and generating proxy classes at runtime in Other platforms. More usage of lazy-initialization will be available in later series.
- Lazy<T> is immutable (just like Nullable<T>)
- Lazy<T> is not thread safe at present
- The record type (Lazy<T>) should be the preferred choice in order to construct such an instance by Reflection
- To avoid a compiler bug in D2010 & XE, we renamed the constructor Create(value) to CreateFrom(value)
Note: This post is a part of Spring4D in Action Series.
Copyright 2012 (c) Baoquan Zuo