Highlander2 Beta: Generics in Delphi for .NET
CodeGear's Delphi Product Manager Nick Hodges has once again released me from an NDA - now for the beta testing of what has been dubbed Highlander2 - the version of RAD Studio 2007 (renamed from BDS 2007) that includes an updated Delphi for .NET personality, compatible with .NET 2.0.
New stuff
There are many new features in this version, including:
- Delphi for .NET now targets .NET 2.0, including full support for Generics (consume and create generic types)
- ASP.NET 2.0 support, CSS templates, etc
- ECO for VCL for .NET
- Vista support in VCL for .NET
- Blackfish, a managed .NET SQL database, supporting writing stored procedures and triggers in any .NET language. [I'm told there is also a Blackfish version for Java]. This is previously known as NDataStore, IIRC.
- Updated and improved help
- Bugfixes for Delphi Win32 and C++Builder
The Delphi Win32 personality receives a number of bug fixes (which will also be included in a free Update 3, I hear), but AFAIK, no significant new features per se. Of course, the IDE enhancements and fixes also benefit Win32 programming.
Since I'm a low-level kind of guy and a self-declared language freak, I'll mostly concentrate on the new language features in Delphi for .NET - particularly Generics.
Generics
Unless you have been living under a rock for the last few years, you'll know that Generics is the ability of programming with types as parameters at compile-time (and, in the case of .NET, at JIT-time). C++ has had templates since 1990 - which is a less constrained (literally), but more complex and potentially more powerful mechanism.
The Delphi for .NET Generics model maps directly to the CLR generics architecture that C# also uses. It is written in the stars (i.e. Delphi roadmap) that generics will also become available for Delphi Win32 in the future - it remains to be seen if this will be a superset or a subset of the current .NET implementation.
Enough talk already, lets look at some generics code in Delphi syntax.
Generic classes
Probably the most common use of generics is to define a class that has generic type parameters. These parameters are provided when a consumer uses the class - in effect instanciating a new concrete class just by declaring it. You can name type parameters anything you like.
type
TGenericClass<T> = class
Field: T;
procedure Method(A: T);
function Foo: T;
property Bar: T read Foo write Method;
end;
function TGenericClass<T>.Foo: T;
begin
Result := Field;
end;
procedure TGenericClass<T>.Method(A: T);
begin
Writeln(TObject(A).ToString);
end;
As you can see in the code above, you (or more often, Code Completion) should repeat both the class name and the generic type parameter list in the implementation of each method. This is to be able to have same-named non-generic and generic classes of different number of type parameters.
To use a generic class, supply the type parameters, and treat the result class like any other class.
type
TIntegerClass = TGenericClass<integer>;
procedure Test;
var
A: TIntegerClass;
B: TGenericClass<integer>;
C: TGenericClass<string>;
begin
A := TIntegerClass.Create;
B := TIntegerClass.Create;
C := TGenericClass<string>.Create;
A.Method(42);
end;
Note that you can use generic types to define new types and inline in variable declaration and in code. Also, different type declarations that map to the same generic type instanciation are assignment compatible.
Generic methods
You can also declare one or more generic type parameters on a method signature. The compiler will generate implementations for each unique type the method is invoked with.
type
// Generic methods - of non-generic class
TFoo = class
procedure GenericMethod<T>(A: T);
function GenericFunction<T>: T;
end;
function TFoo.GenericFunction<T>: T;
begin
Result := Default(T)
end;
procedure TFoo.GenericMethod<T>(A: T);
begin
Writeln(TObject(A).ToString);
end;
When calling a generic method you can either explicitly tell the compiler what type to use for the generic type parameter, or you can have the compiler deduce the type from the actual argument types.
var
Foo: TFoo;
begin
Foo := TFoo.Create;
Foo.GenericMethod<integer>(42);
Foo.GenericMethod(13);
end;
Generic methods can be declared both in generic types or in plain non-generic types.
type
// Generic method - of generic class
TGenericClass<T, U>= class
procedure GenericMethod<V>(A: T; B: U; C:V);
end;
procedure TGenericClass<T, U>.GenericMethod<V>(A: T; B: U; C: V);
begin
end;
Although, one might think that having support for generic properties as well would be a natural extension (after all a property just maps to getter and setter methods, after all), but indeed this is not possible. I don't think C# supports this, either, btw.
type
// Generic property - not supported
TBar = class
procedure SetGenericProperty<T>(const A: T);
function GetGenericProperty<T>: T;
// NOTE: Is this not allowed - Would be cool?!
property GenericProperty<T>: T read GetGenericProperty<T> write SetGenericProperty<T>;
end;
Generic interfaces
Interfaces can now also be declared with generic type parameters. This allows generic classes implement a whole class of interfaces (for all the possible type parameters).
type
IGenericInterface<T> = interface
procedure Handle(A: T);
end;
Using generic interfaces instead of plain interfaces can improve performance, because you don't have to sort to late-bound checks and casts (or boxing) to and from TObject.
The .NET 2.0 specific System.Collections.Generic namespace provides a number of generic interfaces that the generic collection classes use and implement, such as IComparer<T> and IEnumerable<T>.
Generic records
Just as you can have generic classes, you can have generic records. They work exactly the same, the difference is that records implement value types that are stored inline and not via a reference.
type
TGenericRecord<T> = record
Field: T;
procedure DoSomething(A: T);
function GetSomething: T;
property SomeThing: T read GetSomething write DoSomething;
end;
procedure TGenericRecord<T>.DoSomething(A: T);
begin
Writeln(TObject(A).ToString);
end;
function TGenericRecord<T>.GetSomething: T;
begin
Result := Default(T)
end;
This makes it possible to write low- or zero-cost (combined with inlining) encapsulations of generic field wrappers, for instance.
Again, generic methods on records (normal or generic) is supported.
type
TAnyRecord = record
procedure DoSomething<T>(A: T);
end;
procedure TAnyRecord.DoSomething<T>(A: T);
begin
Writeln(TObject(A).ToString);
end;
Generic event types
Method pointers or event types can also include generic type parameters now.
type
// Generic event type
TOnGetValue<T> = procedure (Sender: TObject; var Value: T) of object;
You can use this event type in any normal class, by providing a concrete type parameter, or you can use it inside a generic classs forwarding the class' type parameter.
type
// Using a generic event type
TBar<T> = class
private
FOnGetValue: TGetValueEvent<T>;
public
property OnGetValue: TGetValueEvent<T> read FOnGetValue write FOnGetValue;
end;
Constraints
None of the examples so far has had any constraints on their generic type parameters. This means that you can supply any type arguments for these classes (i.e. integer, IInterface, TObject, TMyObject, boolean, etc) - so it gives you maximum flexibility. The downside is that you cannot do much with the values of generic type parameters - other than storing them, passing them in parameters and comparing or assigning the default value.
Unlike C++ templates, .NET Generics is an explicitly constrained system where you cannot perform any operations on generic parameters and values unless there are one or more "rules" (or constraints) on what the type parameter. In C++ the constraints are implicit; if you try to instanciate an illegal combination of template type parameters, you typically get long and cryptic error messages.
In Delphi.Net the constraints are specified by using a colon following by one or more constraints (much like you use Name: Type pattern for normal parameters, you use a TypeName: Constraints syntax for generic type parameters).
type
// Generic type constraints
TConstrainedGeneric<
T; // No constraints
U: class;
V: constructor;
W: record;
X: TFoo;
Y: class, IMyInterface;
Z: IGenericInterface<T>> = class
end;
As you can see above, there are several types of constraints and most of them are self-explanatory and can be combined in the expected ways (record is incompatible with class and constructor, constructor implies class, and so on). You can constrain a type parameter to a specific class type or descendants (as TFoo above), but not a specific record type.
In .NET both classes and records can implement interfaces, so they are the most generic [sic] type of constraints and often used to call specific methods inherited from the interface.
Default values
When writing generic code, you'll often find the need to compare or assign the default value of the type parameter (nil for classes, all zero bits for records), but until now there has been no syntax to do this. In Win32 you could use FillChar, but this is not type safe (and thus not supported in .NET) and is not a function you can use in expressions.
There is now a new intrinsic function called Default that takes a generic type parameter and returns an the default value (nil or zero-filled record).
procedure TFoo<T>.GetValue(Sender: TObject; var Value: T);
begin
Writeln('Before:', TObject(Value).ToString);
Value := Default(T);
Writeln('After:', TObject(Value).ToString);
end;
Default(T) can be used on all type parameters, even if they have no constraints.
Creating instances of type parameters
With one or more constraints specified on the type parameters, you can start to do more interesting things with the type values. For instance, a type that has a constructor constraint can be used to create new instances of the type.
type
// Creating an instance of a type parameter
TCreateGeneric<V: constructor> = class
private
FV: V;
public
procedure Test;
end;
procedure TCreateGeneric<V>.Test;
begin
FV := V.Create;
end;
Without the constructor constraint on the V type parameter, this code does not compile;
[DCC Error] TestGenerics1.dpr(134): E2018 Record, object or class type required
Conclusion
There is much more to the RAD Studio 2007 version than I have room for here - take a look at some other beta bloggers, Nick's blog and information at CodeGear's site.
There is no doubt that this is the best and most functional version of Delphi/BDS/RAD Studio ever. It includes updated versions of the Delphi Win32 and C++Builder personalities, and brings the Delphi for .NET personality a whole generation step forward.
If you are still on the fence with an older Win32-only version of Delphi (D7 and earlier), upgrading now is a no-brainer. Consider getting Software Assurance as well. If you are a .NET programmer, this is a must-have release, of course. Getting no-fuss access to .NET 2.0 including powerful support for consuming and creating generic types is finally here.
If you are only interested in Win32 programming and you already have Delphi 2007 with no SA, you may want to consider waiting until the next version that (according to the roadmap) should bring generics to Win32. Or better yet, upgrade now, get SA and start playing with and learning generics - it can quickly become addictive :).
15 comments:
Hallvard --
Great summary. Thanks --
Nick
You show an example of a working generic property in the record example. How is that different from the "missing" feature you say C# and Delphi don't support? It seems to me like it does what you ask.
Craig:
The difference is that the record's property just uses the type paramter from the record - that works fine:
property SomeThing: T
What doesn't work is introducing a new type parameter on the property itself:
property SomeThing<U>: U
Even though it does work on methods:
function SomeThing<U>: U;
Nice overview - thanks for posting! I'm very much looking forward to this in the next major native-based release. Hopefully they implement this for properties, too.
I'm curious how this would look on the native side in terms of RTTI. I'm a big fan of Delphi's RTTI and I'd like to see it extended more than it currently is - ie, generating RTTI for more than just published members; an 'official' ('this won't change, guaranteed') API for accessing it, etc. Do you have any thoughts on how RTTI information expressing generics should hopefully work?
How about nullable types?
> How about nullable types?
Good question! ;)
Nullable types are based on generics and the Nullable<T> generic struct. Currently it seems that Delphi for .NET does not have any special shortcut-syntax for this (such as int? in C# for Nullable<int>), but you can use the Nullable type just like any other generic type:
var
i: Nullable<integer>;
begin
if i.HasValue then / ... etc.
Hi Hallvard,
nice to read another post from you! A month ago I tried to write an email regarding your last blog post. Did you receive that?
I see what you're saying, Hallvard, but the potential for confusion seems very high with that proposed feature, both because people might think you have to do that to have a property of a generic type, and because when use "as designed" it effectively means you have multiple, overloaded properties of different types -- something Delphi doesn't allow in other contexts.
By the way, thanks for the informative post!
How about generics and metaclasses/class variables?
Nice write up Hallvard, thanks for posting. I just stumbled across the Nullable Types support last week and posted about it, but I've updated to link to this article now.
Cheers
Malcolm
Does the Generics implementation in Delphi require that the source code for the template definition be available?
How would I distribute an API library (say) that supports generics without exposing the inner workings of the code?
David Jameson
David:
>Does the Generics implementation in Delphi require that the source code for the template definition be available?
No. As with all .NET languages generic classes can be compiled into assemblies and shared with other programmers and languages - no source required. The assemblies contain fairly highlevel CIL code and meta-data - including information about generic classes and type parameters.
OTHO - as a developer looking for good generic libraries - one of my main criterias would be availability of source code.
C5 - an advanced open source generic library written in C# - looks very interesting in this regard.
> How would I distribute an API library (say) that supports generics without exposing the inner workings of the code?
As assemblies. Plain CIL is fairly easy to reverse-engineer back into source code (see Reflector), but there are obfuscation tools that reduces this problem.
There is no Win32 support for Generics yet, but when it comes I assume that the primary distribution of generic code will be source. The alternative would be to distribute .dcu/.dcupl/.bpl packages with the code - but then you would be locked to a specific version of the compiler and RTL. Time will tell.
Thanks for your response.
David
Hi,
Thanks for this article. I found it very useful. As a beginner to generics, I found it a bit advanced. But yes, I did go through other articles before I went through this.
http://www.kanbal.com/index.php?/Net-Frame-Work/generics-in-net-20.html[^][^]
The above can be used if you are a begineer like me. Once you read the above, this article would be cake walk. Ofcourse this article would give a completeness to the understanding of generics.
Hi dude,
I read your blog. This is a wonderful blog. I was able to get the information that I had been looking for. Thanks once again.
-
Hire Delphi Developers
Post a Comment