Som jeg skrev i slutningen af mit indlæg, får man med brugen af TObjectList<T> eller TList<T> kun en standard enumerator - altså man kan "kun" benytte sig af den data type som den typestærke klasse indeholder. Hvis man vil noget andet, skal man stadig selv implementere sin enumerator.
Jeg vil i dette indlæg beskrive, hvordan man laver sine egne enumerators på en generisk klasse. Jeg har brugt en klasse, der kan vise helligdage i forskellige lande (Tyskland, Danmark, Norge, Sverige, USA, Grønland og Frankrig) på henholdsvis engelsk og det respektive sprog. Jeg vil i det følgende kun vise uddrag af koden, det komplette eksempel kan hentes her: Helligdage demo.
Lad mig starte med lidt klasse-definitioner, her bragt i uddrag :
type THoliDay = class strict private .... public constructor Create(const Native, English: string; aDate: TDateTime); property NativeName: string read FNativeName write SetNativeName; property EnglishName: string read FEnglishName write SetEnglishName; property Date: TDateTime read FDate write SetDate; property DateAsStr: string read GetDateAsStr; property FriendlyDate: Boolean read FFriendlyDate write SetFriendlyDate; end;
THolidayList = class(TObjectList<THoliDay>)
... public ... property NativeNames: TNativeNameCollection read GetNativeNames; property EnglishNames: TEnglishNameCollection read GetEnglishNames; end;
Så har jeg en klasse, der binder det hele sammen og regner på påsken, mm.
type TStdDato = class(THolidayList) .... public constructor Create; function GetEasterDay(Year: Word = 0): TDateTime; procedure MakeHoliDays(Year: Word = 0); function GetHoliDaysInMonth(Month: Integer = 0; Year: Integer = 0): THolidayList; function GetHoliDayName(aDate: TDateTime): string; property HoliDays[Index: Integer]: THoliDay read GeTHoliDay; published property Country: TCountryLocale read FCountry write SetCountry; property Year: Integer read FYear write SetYear; property MarkSunday: Boolean read FMarkSunday write SetMarkSunday; property MarkHolidays: Boolean read FMarkHolidays write SetMarkHolidays; property Language: TLanguage read FLanguage write SetLanguage; property SundayStr: string read FSundayStr; end;
Så langt så godt. Indtil videre er der ikke noget mystisk ved det.
Som det er nu, kan jeg gøre det følgende, når jeg skal bruge klassen TStdDato:
var STDDato: TStdDato; begin STDDato := TStdDato.Create; for HolyDay in STDDato do begin ... end; FreeAndNil(STDDato); end;
Men det jeg ønsker at opnå er:
var
STDDato: TStdDato; aName: string; begin STDDato := TStdDato.Create; ListBox1.Clear; for aName in STDDato.EnglishNames do ListBox1.Items.Add(aName); FreeAndNil(STDDato); end;
Bemærk at jeg benytter mig af en string som enumerator. Det kræver man implementerer det selv.
Det første der skal gøres er, at implementere en nedarvning af TEnumerator<string>. Den har jeg valgt at lægge som en public type på THolidayList, da det er der den hører hjemme. Selve klassen minder meget om den vi så ovre i mit Delphi 2007 eksempel:
type THolidayList = class(TObjectList<THoliDay>) public type TEnglishNameEnumerator = class(TEnumerator<string>) private FList: THolidayList; FIndex: Integer; function GetCurrent: string; virtual; protected function DoGetCurrent: string; override; function DoMoveNext: Boolean; override; public constructor Create(aList: THolidayList); property Current: string read GetCurrent; function MoveNext: Boolean; end;
...
Implementeringen af klassen er triviel og kan ses i det komplette eksempel.
Nu hvor vi skal tilføje en enumerator til vores klasse, kræver det endnu en klasse som i dette tilfælde implementerer en nedarvning af TEnumerable<string>:
type TEnglishNameCollection = class(TEnumerable<string>) private FList: THolidayList; function GetCount: Integer; protected function DoGetEnumerator: TEnumerator<string>; override; public constructor Create(ADictionary: THolidayList); function GetEnumerator: TEnglishNameEnumerator; reintroduce; function ToArray: TArray<string>; override; final; property Count: Integer read GetCount; end;Implementeringen af den er også ganske ligetil, så den vil jeg udelade her.
Til sidst skal vi bare have det hele koblet på klassen, det gøres således:
type THolidayList = class(TObjectList<THoliDay>) .... private FEnglishNames: TEnglishNameCollection; ... function GetEnglishNames: TEnglishNameCollection; public ... property EnglishNames: TEnglishNameCollection read GetEnglishNames; end; ... function THolidayList.GetEnglishNames: TEnglishNameCollection; begin if FEnglishNames = nil then FEnglishNames := TEnglishNameCollection.Create(Self); Result := FEnglishNames; end;
Hermed har vi opnået vores mål. Den nye enumerator kan nu bruges som vi ønskede:
var STDDato: TStdDato; aName: string; begin STDDato := TStdDato.Create; ListBox1.Clear; for aName in STDDato.EnglishNames do ListBox1.Items.Add(aName); FreeAndNil(STDDato); end;
Som sagt var der ikke mange ben i det, men jeg synes det skulle med alligevel for at gøre dette indlæg komplet.type THolidayList = class(TObjectList<THoliDay>) public type ... TNativeNameEnumerator = class(THolidayList.TEnglishNameEnumerator) private function GetCurrent: string; override; end; ... TNativeNameCollection = class(THolidayList.TEnglishNameCollection) public function GetEnumerator: TNativeNameEnumerator; reintroduce; end; private ... FNativeNames: TNativeNameCollection; ... function GetNativeNames: TNativeNameCollection; public ... property NativeNames: TNativeNameCollection read GetNativeNames; end; ... { THolidayList.TNativeNameEnumerator } function THolidayList.TNativeNameEnumerator.GetCurrent: string; begin Result := FList[FIndex].NativeName; end; { THolidayList.TNativeNameCollection } function THolidayList.TNativeNameCollection.GetEnumerator: TNativeNameEnumerator; begin Result := TNativeNameEnumerator.Create(FList); end;
Her til slut vil jeg lige endnu en gang nævne at hele eksemplet kan hentes her: Helligdage demo og i den forbindelse vil jeg da lige vise er skærmbillede derfra :
Jens Borrisholt
Ingen kommentarer:
Send en kommentar