Constraints can be associated with a type parameter of a generic. Constraints declare items that must be supported by any concrete type passed to that parameter in a construction of the generic type.
Constraint items include:
You can specify both "constructor" and "class" for a constraint. However, "record" cannot be combined with other reserved words. Multiple constraints act as an additive union ("AND" logic).
The examples given here show only class types, although constraints apply to all forms of generics.
Constraints are declared in a fashion that resembles type declarations in regular parameter lists:
type TFoo<T: ISerializable> = class FField: T; end;
In the example declaration given here, the 'T' type parameter indicates that it must support the ISerializable interface. In a type construction like TFoo<TMyClass>, the compiler checks at compile time to ensure that TMyClass actually implements ISerializable.
When you specify constraints, you separate multiple type parameters by semicolons, as you do with a parameter list declaration:
type TFoo<T: ISerializable; V: IComparable>
Like parameter declarations, multiple type parameters can be grouped together in a comma list to bind to the same constraints:
type TFoo<S, U: ISerializable> ...
In the example above, S and U are both bound to the ISerializable constraint.
Multiple constraints can be applied to a single type parameters as a comma list following the colon:
type TFoo<T: ISerializable, ICloneable; V: IComparable> ...
Constrained type parameters can be mixed with “free” type parameters. For example, all the following are valid:
type TFoo<T; C: IComparable> ... TBar<T, V> ... TTest<S: ISerializable; V> ... // T and V are free, but C and S are constrained
A type parameter constraint may contain zero, one, or a comma separated list of multiple interface types.
A type parameter constrained by an interface type means that the compiler will verify at compile time that a concrete type passed as an argument to a type construction implements the specified interface type(s).
type TFoo<T: ICloneable> ... TTest1 = class(TObject, ICloneable) ... end; TError = class end; var X: TFoo<TTest1>; // TTest1 is checked for ICloneable support here // at compile time Y: TFoo<TError>; // exp: syntax error here - TError does not support // ICloneable
A type parameter may be constrained by zero or one class type. As with interface type constraints, this declaration means that the compiler will require any concrete type passed as an argument to the constrained type param to be assignment compatible with the constraint class.
Compatibility of class types follows the normal rules of OOP type compatibilty - descendent types can be passed where their ancestor types are required.
A type parameter may be constrained by zero or one instance of the reserved word "constructor". This means that the actual argument type must be a class that defines a default constructor (a public parameterless constructor), so that methods within the generic type may construct instances of the argument type using the argument type's default constructor, without knowing anything about the argument type itself (no minimum base type requirements).
In a constraint declaration, you can mix “constructor” in any order with interface or class type constraints.
A type parameter may be constrained by zero or one instance of the reserved word "class". This means that the actual type must be a reference type, that is, a class or interface type.
A type parameter may be constrained by zero or one instance of the reserved word "record". This means that the actual type must be a value type (not a reference type). A “record” constraint cannot be combined with a “class” or “constructor” constraint.
When using a field or variable of a constrained type parameter, it is not necessary in many cases to typecast in order to treat the field or variable as one of the constrained types. The compiler can infer which type you're referring to by looking at the method name and by performing a variation of overload resolution over the union of the methods sharing the same name across all the constraints on that type.
type TFoo<T: ISerializable, ICloneable> = class FData: T; procedure Test; end; procedure TFoo<T>.Test; begin FData.Clone; end;
The compiler looks for "Clone" methods in ISerializable and ICloneable, since FData is of type T, which is guaranteed to support both those interfaces. If both interfaces implement "Clone" with the same parameter list, the compiler issues an ambiguous method call error and require you to typecast to one or the other interface to disambiguate the context.
Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!