{ *************************************************************************** Copyright (c) 2016-2021 Kike Pérez Unit : Quick.MemoryCache Description : Cache objects with expiration control Author : Kike Pérez Version : 1.0 Created : 14/07/2019 Modified : 17/05/2021 This file is part of QuickLib: https://github.com/exilon/QuickLib *************************************************************************** Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *************************************************************************** } unit Quick.MemoryCache; {$i QuickLib.inc} interface uses System.SysUtils, System.Generics.Collections, System.DateUtils, System.TypInfo, RTTI, Quick.Commons, Quick.Value, Quick.Threads, Quick.Cache.Intf, Quick.MemoryCache.Types, Quick.MemoryCache.Serializer.Json, Quick.MemoryCache.Compressor.GZip; type TCacheFlushedEvent = reference to procedure(aRemovedEntries : Integer); TBeginPurgerJobEvent = reference to procedure; TEndPurgerJobEvent = reference to procedure(aPurgedEntries : Integer); TPurgerJobErrorEvent = reference to procedure(const aErrorMsg : string); IMemoryCache = interface ['{57927AD7-C993-4C3C-B552-43A39F99E73A}'] function GetCompression: Boolean; procedure SetCompression(const Value: Boolean); function GetCachedObjects: Integer; function GetCacheSize: Integer; procedure SetOnBeginPurgerJob(const Value: TBeginPurgerJobEvent); procedure SetOnCacheFlushed(const Value: TCacheFlushedEvent); procedure SetOnEndPurgerJob(const Value: TEndPurgerJobEvent); procedure SetOnPurgerJobError(const Value: TPurgerJobErrorEvent); property Compression : Boolean read GetCompression write SetCompression; property CachedObjects : Integer read GetCachedObjects; property CacheSize : Integer read GetCacheSize; property OnCacheFlushed : TCacheFlushedEvent write SetOnCacheFlushed; property OnBeginPurgerJob : TBeginPurgerJobEvent write SetOnBeginPurgerJob; property OnEndPurgerJob : TEndPurgerJobEvent write SetOnEndPurgerJob; property OnPurgeJobError : TPurgerJobErrorEvent write SetOnPurgerJobError; procedure SetValue(const aKey : string; aValue : T; aExpirationMillisecons : Integer = 0); overload; procedure SetValue(const aKey : string; aValue : T; aExpirationDate : TDateTime); overload; function GetValue(const aKey : string) : T; function TryGetValue(const aKey : string; out aValue : T) : Boolean; procedure RemoveValue(const aKey : string); procedure Refresh(const aKey: string; aExpirationMilliseconds : Integer); procedure Flush; end; IMemoryCache = interface(ICache) ['{F109AE78-43D7-4983-B8ED-52A41533EEED}'] function GetCompression: Boolean; procedure SetCompression(const Value: Boolean); function GetCachedObjects: Integer; function GetCacheSize: Integer; procedure SetOnBeginPurgerJob(const Value: TBeginPurgerJobEvent); procedure SetOnCacheFlushed(const Value: TCacheFlushedEvent); procedure SetOnEndPurgerJob(const Value: TEndPurgerJobEvent); procedure SetOnPurgerJobError(const Value: TPurgerJobErrorEvent); property Compression : Boolean read GetCompression write SetCompression; property CachedObjects : Integer read GetCachedObjects; property CacheSize : Integer read GetCacheSize; property OnCacheFlushed : TCacheFlushedEvent write SetOnCacheFlushed; property OnBeginPurgerJob : TBeginPurgerJobEvent write SetOnBeginPurgerJob; property OnEndPurgerJob : TEndPurgerJobEvent write SetOnEndPurgerJob; property OnPurgeJobError : TPurgerJobErrorEvent write SetOnPurgerJobError; procedure SetValue(const aKey : string; aValue : TObject; aExpirationMilliseconds : Integer = 0); overload; procedure SetValue(const aKey : string; aValue : TObject; aExpirationDate : TDateTime); overload; procedure SetValue(const aKey, aValue : string; aExpirationMilliseconds : Integer = 0); overload; procedure SetValue(const aKey, aValue : string; aExpirationDate : TDateTime); overload; procedure SetValue(const aKey : string; aValue : TArray; aExpirationMilliseconds : Integer = 0); overload; procedure SetValue(const aKey : string; aValue : TArray; aExpirationDate : TDateTime); overload; procedure SetValue(const aKey : string; aValue : TArray; aExpirationMilliseconds : Integer = 0); overload; procedure SetValue(const aKey : string; aValue : TArray; aExpirationDate : TDateTime); overload; function GetValue(const aKey : string) : string; overload; function TryGetValue(const aKey : string; aValue : TObject) : Boolean; overload; function TryGetValue(const aKey : string; out aValue : string) : Boolean; overload; function TryGetValue(const aKey : string; out aValue : TArray) : Boolean; overload; function TryGetValue(const aKey : string; out aValue : TArray) : Boolean; overload; procedure RemoveValue(const aKey : string); procedure Refresh(const aKey: string; aExpirationMilliseconds : Integer); procedure Flush; end; TCacheEntry = class(TInterfacedObject,ICacheEntry) private fCreationDate : TDateTime; fExpiration : Cardinal; fExpirationDate : TDateTime; fCompression : Boolean; fCompressor : ICacheCompressor; fData : string; fIsCompressed : Boolean; function GetCreationDate: TDateTime; function GetData: string; function GetExpiration: Cardinal; procedure SetCreationDate(const Value: TDateTime); procedure SetData(const Value: string); procedure SetExpiration(aMilliseconds : Cardinal); function GetExpirationDate: TDateTime; procedure SetExpirationDate(const Value: TDateTime); public constructor Create(aCompression : Boolean; aCacheCompressor : ICacheCompressor); property CreationDate : TDateTime read GetCreationDate write SetCreationDate; property Expiration : Cardinal read GetExpiration write SetExpiration; property ExpirationDate : TDateTime read GetExpirationDate write SetExpirationDate; property Data : string read GetData write SetData; function Size : Integer; function IsExpired : Boolean; end; TMemoryCacheBase = class(TInterfacedObject) private fPurgerInterval : Integer; fMaxSize : Integer; fCachedObjects : Integer; fCacheSize : Integer; fCompression : Boolean; fLock : TMultiReadExclusiveWriteSynchronizer; fCacheJobs : TScheduledTasks; fOnCacheFlushed : TCacheFlushedEvent; fOnPurgerJobError : TPurgerJobErrorEvent; fOnBeginPurgerJob : TBeginPurgerJobEvent; fOnEndPurgerJob : TEndPurgerJobEvent; procedure CreatePurgerJobs; procedure RemoveExpiredCacheEntries; virtual; procedure SetPurgerInterval(const Value: Integer); protected fItems : TDictionary; fSerializer : ICacheSerializer; fCompressor : ICacheCompressor; function GetCachedObjects: Integer; function GetCacheSize: Integer; procedure SetOnBeginPurgerJob(const Value: TBeginPurgerJobEvent); procedure SetOnCacheFlushed(const Value: TCacheFlushedEvent); procedure SetOnEndPurgerJob(const Value: TEndPurgerJobEvent); procedure SetOnPurgerJobError(const Value: TPurgerJobErrorEvent); function GetCompression: Boolean; procedure SetCompression(const Value: Boolean); public constructor Create(aPurgerInterval : Integer = 20; aCacheSerializer : ICacheSerializer = nil; aCacheCompressor : ICacheCompressor = nil); virtual; destructor Destroy; override; property MaxSize : Integer read fMaxSize write fMaxSize; property PurgerInterval : Integer read fPurgerInterval; property Compression : Boolean read GetCompression write SetCompression; property CachedObjects : Integer read GetCachedObjects; property CacheSize : Integer read GetCacheSize; property OnCacheFlushed : TCacheFlushedEvent read fOnCacheFlushed write SetOnCacheFlushed; property OnBeginPurgerJob : TBeginPurgerJobEvent read fOnBeginPurgerJob write SetOnBeginPurgerJob; property OnEndPurgerJob : TEndPurgerJobEvent read fOnEndPurgerJob write SetOnEndPurgerJob; property OnPurgeJobError : TPurgerJobErrorEvent read fOnPurgerJobError write SetOnPurgerJobError; procedure RemoveValue(const aKey : string); virtual; procedure Refresh(const aKey: string; aExpirationMilliseconds : Integer); procedure Flush; virtual; end; TMemoryCache = class(TMemoryCacheBase,IMemoryCache) private procedure SetValue(const aKey : string; aValue : T; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime); overload; public constructor Create(aPurgerInterval : Integer = 20; aCacheSerializer : ICacheSerializer = nil; aCacheCompressor : ICacheCompressor = nil); override; procedure SetValue(const aKey : string; aValue : T; aExpirationMillisecons : Integer = 0); overload; procedure SetValue(const aKey : string; aValue : T; aExpirationDate : TDateTime); overload; function GetValue(const aKey : string) : T; function TryGetValue(const aKey : string; out oValue : T) : Boolean; procedure RemoveValue(const aKey : string); override; end; TMemoryCache = class(TMemoryCacheBase,IMemoryCache) private procedure SetValue(const aKey: string; aValue: TObject; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime); overload; procedure SetValue(const aKey, aValue: string; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime); overload; public procedure SetValue(const aKey, aValue : string; aExpirationMilliseconds : Integer = 0); overload; procedure SetValue(const aKey, aValue : string; aExpirationDate : TDateTime); overload; procedure SetValue(const aKey : string; aValue : TObject; aExpirationMilliseconds : Integer = 0); overload; procedure SetValue(const aKey : string; aValue : TObject; aExpirationDate : TDateTime); overload; procedure SetValue(const aKey : string; aValue : TArray; aExpirationMilliseconds : Integer = 0); overload; procedure SetValue(const aKey : string; aValue : TArray; aExpirationDate : TDateTime); overload; procedure SetValue(const aKey : string; aValue : TArray; aExpirationMilliseconds : Integer = 0); overload; procedure SetValue(const aKey : string; aValue : TArray; aExpirationDate : TDateTime); overload; function GetValue(const aKey : string) : string; overload; function TryGetValue(const aKey : string; out aValue : string) : Boolean; overload; function TryGetValue(const aKey : string; aValue : TObject) : Boolean; overload; function TryGetValue(const aKey : string; out oValue : T) : Boolean; overload; function TryGetValue(const aKey : string; out aValue : TArray) : Boolean; overload; function TryGetValue(const aKey : string; out aValue : TArray) : Boolean; overload; end; EMemoryCacheConfigError = class(Exception); EMemoryCacheSetError = class(Exception); EMemoryCacheGetError = class(Exception); EMemoryCacheFlushError = class(Exception); implementation { TMemoryCacheBase } constructor TMemoryCacheBase.Create(aPurgerInterval : Integer = 20; aCacheSerializer : ICacheSerializer = nil; aCacheCompressor : ICacheCompressor = nil); begin fCompression := True; SetPurgerInterval(aPurgerInterval); fCachedObjects := 0; fCacheSize := 0; fLock := TMultiReadExclusiveWriteSynchronizer.Create; if aCacheSerializer <> nil then fSerializer := aCacheSerializer else fSerializer := TCacheJsonSerializer.Create; if aCacheCompressor <> nil then fCompressor := aCacheCompressor else fCompressor := TCacheCompressorGZip.Create; fItems := TDictionary.Create; fCacheJobs := TScheduledTasks.Create; CreatePurgerJobs; fCacheJobs.Start; end; procedure TMemoryCacheBase.CreatePurgerJobs; begin fCacheJobs.AddTask('RemoveExpired',procedure (task : ITask) begin RemoveExpiredCacheEntries; end ).OnException(procedure(task : ITask; aException : Exception) begin if Assigned(fOnPurgerJobError) then fOnPurgerJobError(aException.Message); end ).StartInSeconds(fPurgerInterval).RepeatEvery(fPurgerInterval,TTimeMeasure.tmSeconds); end; destructor TMemoryCacheBase.Destroy; begin fItems.Free; fLock.Free; fCacheJobs.Stop; fCacheJobs.Free; inherited; end; procedure TMemoryCacheBase.Flush; begin fLock.BeginWrite; try fItems.Clear; if Assigned(fOnCacheFlushed) then fOnCacheFlushed(fCachedObjects); fCachedObjects := 0; fCacheSize := 0; finally fLock.EndWrite; end; end; procedure TMemoryCacheBase.Refresh(const aKey: string; aExpirationMilliseconds : Integer); var cacheitem : ICacheEntry; begin if fItems.TryGetValue(aKey,cacheitem) then begin cacheitem.CreationDate := Now; cacheitem.Expiration := aExpirationMilliseconds; end; end; procedure TMemoryCacheBase.RemoveExpiredCacheEntries; var pair : TPair; removedentries : Integer; begin if Assigned(fOnBeginPurgerJob) then fOnBeginPurgerJob; removedentries := 0; fLock.BeginRead; try for pair in fItems do begin if pair.Value.IsExpired then begin fLock.BeginWrite; try //decrease cacheitem size to cachesize AtomicDecrement(fCacheSize,pair.Value.Size); //remove cacheitem from cache fItems.Remove(pair.Key); //decrease cachedobjects AtomicDecrement(fCachedObjects,1); Inc(removedentries); finally fLock.EndWrite; end; end; end; finally fLock.EndRead; if Assigned(fOnEndPurgerJob) then fOnEndPurgerJob(removedentries); end; end; procedure TMemoryCacheBase.RemoveValue(const aKey: string); var cacheitem : ICacheEntry; begin if fItems.TryGetValue(aKey,cacheitem) then begin //decrease cacheitem size to cachesize AtomicDecrement(fCacheSize,cacheitem.Size); //remove cacheitem from cache fItems.Remove(aKey); //decrease cachedobjects AtomicDecrement(fCachedObjects,1); end; end; function TMemoryCacheBase.GetCachedObjects: Integer; begin Result := fCachedObjects; end; function TMemoryCacheBase.GetCacheSize: Integer; begin Result := fCacheSize; end; function TMemoryCacheBase.GetCompression: Boolean; begin Result := fCompression; end; procedure TMemoryCacheBase.SetCompression(const Value: Boolean); begin fCompression := Value; end; procedure TMemoryCacheBase.SetOnBeginPurgerJob(const Value: TBeginPurgerJobEvent); begin fOnBeginPurgerJob := Value; end; procedure TMemoryCacheBase.SetOnCacheFlushed(const Value: TCacheFlushedEvent); begin fOnCacheFlushed := Value; end; procedure TMemoryCacheBase.SetOnEndPurgerJob(const Value: TEndPurgerJobEvent); begin fOnEndPurgerJob := Value; end; procedure TMemoryCacheBase.SetOnPurgerJobError(const Value: TPurgerJobErrorEvent); begin fOnPurgerJobError := Value; end; procedure TMemoryCacheBase.SetPurgerInterval(const Value: Integer); begin if Value > 5 then begin fPurgerInterval := Value; end else raise EMemoryCacheConfigError.Create('Purger Interval must be greater than 5 seconds'); end; { TCacheItem } constructor TCacheEntry.Create(aCompression : Boolean; aCacheCompressor : ICacheCompressor); begin fIsCompressed := False; fCompression := aCompression; fCompressor := aCacheCompressor; end; function TCacheEntry.GetCreationDate: TDateTime; begin Result := fCreationDate; end; function TCacheEntry.GetData: string; begin if fIsCompressed then Result := fCompressor.Decompress(fData) else Result := fData; end; function TCacheEntry.GetExpiration: Cardinal; begin Result := fExpiration; end; function TCacheEntry.GetExpirationDate: TDateTime; begin Result := fExpirationDate; end; procedure TCacheEntry.SetCreationDate(const Value: TDateTime); begin fCreationDate := Value; end; procedure TCacheEntry.SetExpiration(aMilliseconds: Cardinal); begin fExpiration := aMilliseconds; fExpirationDate := IncMilliSecond(fCreationDate,fExpiration); end; procedure TCacheEntry.SetExpirationDate(const Value: TDateTime); begin fExpiration := MilliSecondOf(Value); fExpirationDate := Value; end; function TCacheEntry.IsExpired: Boolean; begin if fExpiration = 0 then Result := False else Result := Now() > fExpirationDate; end; procedure TCacheEntry.SetData(const Value: string); begin fIsCompressed := False; //var a := value; //var b := value.Length; if fCompression then begin if ((Value.Length + 1) * 2) > 1024 then begin fData := fCompressor.Compress(Value); fIsCompressed := True; end else begin fData := Value; end; end else fData := Value; end; function TCacheEntry.Size: Integer; begin //Result := (fData.Length + 1) * SizeOf(Char); Result := (fData.Length + 1) * StringElementSize(fData); end; { TMemoryCache } constructor TMemoryCache.Create(aPurgerInterval: Integer; aCacheSerializer: ICacheSerializer; aCacheCompressor: ICacheCompressor); begin inherited Create(aPurgerInterval,aCacheSerializer,aCacheCompressor); end; function TMemoryCache.GetValue(const aKey: string): T; begin TryGetValue(aKey,Result); end; procedure TMemoryCache.RemoveValue(const aKey: string); begin inherited RemoveValue(aKey); end; procedure TMemoryCache.SetValue(const aKey: string; aValue: T; aExpirationDate: TDateTime); begin SetValue(aKey,aValue,0,aExpirationDate); end; procedure TMemoryCache.SetValue(const aKey: string; aValue: T; aExpirationMillisecons: Integer); begin SetValue(aKey,aValue,aExpirationMillisecons,0.0); end; procedure TMemoryCache.SetValue(const aKey: string; aValue: T; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime); var cacheitem : TCacheEntry; begin fLock.BeginWrite; try cacheitem := TCacheEntry.Create(fCompression,fCompressor); cacheitem.CreationDate := Now(); cacheitem.Expiration := aExpirationMilliseconds; if aExpirationDate > 0.0 then cacheitem.ExpirationDate := aExpirationDate; //add object to cache case PTypeInfo(TypeInfo(T))^.Kind of tkClass, tkPointer : begin //object type need to be serialized cacheitem.Data := fSerializer.Serialize(PObject(@aValue)^); end; tkString, tkWideString, tkUString, tkChar, tkWideChar : cacheitem.Data := string((@aValue)^); {$IFNDEF NEXTGEN} tkAnsiString : cacheitem.Data := string(AnsiString((@aValue)^)); {$ENDIF} else begin raise EMemoryCacheSetError.Create('Type not supported as cache'); end; end; RemoveValue(aKey); fItems.Add(aKey,cacheitem); //add cacheitem size to cachesize AtomicIncrement(fCacheSize,cacheitem.Size); //increment cacheobjects AtomicIncrement(fCachedObjects,1); finally fLock.EndWrite; end; end; function TMemoryCache.TryGetValue(const aKey: string; out oValue: T): Boolean; var cacheitem : ICacheEntry; flexvalue : TFlexValue; obj : TObject; begin fLock.BeginRead; try Result := fItems.TryGetValue(aKey,cacheitem); //check if cacheitem already expired if Result and cacheitem.IsExpired then Exit(False); finally fLock.EndRead; end; if Result then begin flexvalue.AsString := cacheitem.Data; case PTypeInfo(TypeInfo(T))^.Kind of tkInteger : oValue := TValue.From(flexvalue.AsInteger).AsType; tkInt64 : oValue := TValue.From(flexvalue.AsInt64).AsType; tkFloat : begin if TypeInfo(T) = TypeInfo(TDateTime) then oValue := TValue.From(flexvalue.AsDateTime).AsType else oValue := TValue.From(flexvalue.AsExtended).AsType; end; tkString, tkUString : oValue := TValue.From(flexvalue.AsString).AsType; {$IFDEF MSWINDOWS} tkAnsiString : oValue := TValue.From(flexvalue.AsAnsiString).AsType; tkWideString : oValue := TValue.From(flexvalue.AsWideString).AsType; {$ENDIF} tkEnumeration : begin if TypeInfo(T) = TypeInfo(Boolean) then oValue := TValue.From(flexvalue.AsBoolean).AsType else oValue := TValue.From(flexvalue.AsInteger).AsType; end; tkClass, tkPointer : begin obj := PTypeInfo(TypeInfo(T))^.TypeData.ClassType.Create; fSerializer.Deserialize(cacheitem.Data,obj); oValue := TValue.From(obj).AsType; //oValue := T((@obj)^); end else raise EMemoryCacheGetError.Create('Error casting value from cache'); end; end; end; { TMemoryCache } function TMemoryCache.GetValue(const aKey: string): string; begin TryGetValue(aKey,Result); end; procedure TMemoryCache.SetValue(const aKey, aValue: string; aExpirationMilliseconds: Integer); begin SetValue(aKey,aValue,aExpirationMilliseconds,0.0); end; procedure TMemoryCache.SetValue(const aKey, aValue: string; aExpirationDate: TDateTime); begin SetValue(aKey,aValue,0,aExpirationDate); end; procedure TMemoryCache.SetValue(const aKey: string; aValue: TObject; aExpirationMilliseconds: Integer); begin SetValue(aKey,aValue,aExpirationMilliseconds,0.0); end; procedure TMemoryCache.SetValue(const aKey: string; aValue: TObject; aExpirationDate: TDateTime); begin SetValue(aKey,aValue,0,aExpirationDate); end; procedure TMemoryCache.SetValue(const aKey: string; aValue: TObject; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime); begin SetValue(aKey,fSerializer.Serialize(aValue),aExpirationMilliseconds,aExpirationDate); end; procedure TMemoryCache.SetValue(const aKey, aValue: string; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime); var cacheitem : TCacheEntry; begin fLock.BeginWrite; try cacheitem := TCacheEntry.Create(fCompression,fCompressor); cacheitem.CreationDate := Now(); cacheitem.Expiration := aExpirationMilliseconds; if aExpirationDate > 0.0 then cacheitem.ExpirationDate := aExpirationDate; //add object to cache cacheitem.Data := aValue; RemoveValue(aKey); fItems.Add(aKey,cacheitem); //add cacheitem size to cachesize AtomicIncrement(fCacheSize,cacheitem.Size); //increment cacheobjects AtomicIncrement(fCachedObjects,1); finally fLock.EndWrite; end; end; procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray; aExpirationDate: TDateTime); begin SetValue(aKey,fSerializer.Serialize(aValue),0,aExpirationDate); end; procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray; aExpirationMilliseconds: Integer); begin SetValue(aKey,fSerializer.Serialize(aValue),aExpirationMilliseconds,0.0); end; procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray; aExpirationDate: TDateTime); begin SetValue(aKey,fSerializer.Serialize(aValue),0,aExpirationDate); end; procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray; aExpirationMilliseconds: Integer); begin SetValue(aKey,fSerializer.Serialize(aValue),aExpirationMilliseconds,0.0); end; function TMemoryCache.TryGetValue(const aKey: string; aValue : TObject): Boolean; var cacheitem : ICacheEntry; begin fLock.BeginRead; try if aValue = nil then raise EMemoryCacheGetError.Create('Cannot passed a nil object as param'); Result := fItems.TryGetValue(aKey,cacheitem); //check if cacheitem already expired if (not Result) or (cacheitem.IsExpired) then Exit(False); finally fLock.EndRead; end; fSerializer.Deserialize(cacheitem.Data,aValue); end; function TMemoryCache.TryGetValue(const aKey: string; out aValue: string): Boolean; begin Result := TryGetValue(aKey,aValue); end; function TMemoryCache.TryGetValue(const aKey: string; out oValue: T): Boolean; var cacheitem : ICacheEntry; flexvalue : TFlexValue; obj : TObject; begin fLock.BeginRead; try Result := fItems.TryGetValue(aKey,cacheitem); //check if cacheitem already expired if Result and cacheitem.IsExpired then Exit(False); finally fLock.EndRead; end; if Result then begin flexvalue.AsString := cacheitem.Data; case PTypeInfo(TypeInfo(T))^.Kind of tkInteger : oValue := TValue.From(flexvalue.AsInteger).AsType; tkInt64 : oValue := TValue.From(flexvalue.AsInt64).AsType; tkFloat : begin if TypeInfo(T) = TypeInfo(TDateTime) then oValue := TValue.From(flexvalue.AsDateTime).AsType else oValue := TValue.From(flexvalue.AsExtended).AsType; end; tkString, tkUString : oValue := TValue.From(flexvalue.AsString).AsType; {$IFDEF MSWINDOWS} tkAnsiString : oValue := TValue.From(flexvalue.AsAnsiString).AsType; tkWideString : oValue := TValue.From(flexvalue.AsWideString).AsType; {$ENDIF} tkEnumeration : begin if TypeInfo(T) = TypeInfo(Boolean) then oValue := TValue.From(flexvalue.AsBoolean).AsType else oValue := TValue.From(flexvalue.AsInteger).AsType; end; tkClass, tkPointer : begin obj := PTypeInfo(TypeInfo(T))^.TypeData.ClassType.Create; fSerializer.Deserialize(flexvalue.AsString,obj); oValue := TValue.From(obj).AsType; end; else raise EMemoryCacheGetError.Create('Error casting value from cache'); end; end; end; function TMemoryCache.TryGetValue(const aKey: string; out aValue: TArray): Boolean; var cacheitem : ICacheEntry; begin fLock.BeginRead; try Result := fItems.TryGetValue(aKey,cacheitem); //check if cacheitem already expired if Result and cacheitem.IsExpired then Exit(False); finally fLock.EndRead; end; if Result then fSerializer.Deserialize(cacheitem.Data,aValue); end; function TMemoryCache.TryGetValue(const aKey: string; out aValue: TArray): Boolean; var cacheitem : ICacheEntry; begin fLock.BeginRead; try Result := fItems.TryGetValue(aKey,cacheitem); //check if cacheitem already expired if Result and cacheitem.IsExpired then Exit(False); finally fLock.EndRead; end; if Result then fSerializer.Deserialize(cacheitem.Data,aValue); end; end.