I’d like to present a short class written by my colleague at work. Why? Because I just love how it uses the generics syntax to simplify some operations on enumerated types.
Like me, he hated the hoops one must jump through to convert an instance of an enumerated type to a string, to check if an integer value that was read from some storage contains a valid integer representation of some enumerated value and to enumerate (no pun intended) over such a type. Unlike me, he did something about that and wrote a Spring-inspired Range<T>.
The code fragment below represents a simplified version of this type. I’ve cleaned out various directives (static, overload) and private methods to make it less visually noisy. A link to the full source can be found at the bottom of this post.
type Range<T: record> = record public class function Clip(const value: Integer): T; class function Clip(const value: T): T; class function Ensure(const value: Integer; const min, max: T): T; class function Ensure(const value, min, max: T): T; class function FromInt(const value: Integer): T; class function Enum: RangeEnum<T>; static; class function GetValueOrDefault(const value: Integer): T; class function IsValid(const value: Integer): Boolean; class function IsValid(const value: T): Boolean; class function Max: T; static; class function Min: T; static; class function ToInt(const value: T): Integer; class function ToString(const value: T): string; end;
The usage is best explained through examples and to do that we need an enumerated type. Let’s say we have a very simple type TMyEnum and a variable of this type.
type TMyEnum = (enum1, enum2, enum3); var e: TMyEnum;
Min and Max return exactly the same information as Low() and High() functions. Their usage is mainly internal because in the Range<T> implementation we cannot use Low(T) and High(T) as the compiler doesn’t support that. Range<T> uses RTTI to get the min/max value and because of that Range<T> doesn’t support enumerated types with explicit values (TWontWork = (enum1 = 1, enum2 = 2, enum3 = 4)) which don’t contain runtime type information.
Example: Range<TMyEnum>.Min = enum1. Range<TMyEnum>.Max = enum3.
FromInt and ToInt are conversion functions that work exactly the same as hard casting.
Example: Range<TMyEnum>.FromInt(2) = TMyEnum(2) = enum3.
ToString converts an instance of an enumerated type into its string representation. It uses Delphi’s TValue to do the conversion.
Example: e := enum2; Range<TMyEnum>.ToString(e) = “enum2”.
Ensure clips the input value (either presented as an integer or as a typed value) into some range of values.
Example: Range<TMyEnum>.Ensure(enum3, enum1, enum2) = enum2.
Clip clips the input value to the full range of valid values for that type. It is great for validating input.
Example: Range<TMyEnum>.Clip(3) = enum2.
IsValid verifies that the value lies inside the range of valid values. GetValueOrDefault returns the value if it is valid or Default(T) if it is not.
My favorite (besides the ToString and Clip) is Enum, which returns an enumerator for the type.
Example: for e in Range<TMyEnum>.Enum in is functionally equivalent to for e := Low(TMyEnum) to High(TMyEnum).
If you want to play with Range<T> or just explore the implementation, you can download it from here.