Sunday, September 30, 2007

DN4DP#21: .NET only: Undocumented corner

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

Last time we're covered the differenced between classic single-cast events vs .NET-style multi-cast events. This time we'll dive into an undocumented corner of the language.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Undocumented corner

We have already mentioned the new semi-undocumented record helper feature in Delphi for .NET. Another undocumented and more subtle extension[1] is the ability to initialize global variables and typed constants with simple casting and constructor calls using constant parameters.

type
TFoo = class
constructor Create(A, B, C: integer);
end;
TBar = record
public
class operator Explicit(Value: Integer): TBar;
class operator Implicit(Value: Double): TBar;
end;

var
Foo: TFoo = TFoo.Create(1, 2, 3);
Bar1: TBar = TBar(42);
const
Bar2: TBar = 3.14;

In native Delphi you can initialize global variables with constant expressions such as integers, floating point values and strings. In .NET this has been extended to allow initialization of object references and records using a constructor call or an implicit or explicit cast operator. This feature can’t be used for instance fields or class vars, only for global variables and typed constants, so its usefulness is a little limited. The InitializeGlobals project demonstrates this new syntax.


Note: While this is currently an undocumented feature, it is fairly safe to assume it will continue to be available in the future. For instance, the Currency type in the Borland.Delphi.System unit is implemented as a record with operator overloading and it has implicit conversion operators from Double and Integer. Without this feature, there would be no way to initialize a global Currency variable (breaking existing Win32 code).





[1] This existence of this new syntax was first published by Chee Whee Chua (Borland Singapore) at http://blogs.borland.com/chewy/archive/2005/11/23/22210.aspx "



Update:  The new URL is: http://blogs.codegear.com/chewy/2005/11/23/22210

Friday, September 28, 2007

DN4DP#20: .NET only: Multi-cast events

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

Last time we're scared by unsafe code. Here we discuss the syntax for normal Delphi events vs .NET-style multicast events.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Multi-cast events

Delphi for .NET has full support for both old-style single cast events and .NET style multi cast events.

To write a traditional Delphi event, you first declare a delegate type using the procedure of object syntax, then declare an event property that reference a delegate field in the read and write specifiers.

type
TLevelChangedEvent = procedure (Sender: TObject; NewLevel: integer) of object;
TMyComponent = class
strict private
FOnLevelChanged: TLevelChangedEvent;
public
property OnLevelChanged: TLevelChangedEvent read FOnLevelChanged write FOnLevelChanged;
end;

This is a single-cast event that will compile both in .NET and Win32. It supports direct assignment of a method reference or nil to the event property and it supports directly invoking the event property. For instance, most VCL events are single cast and support assignments like this:

  MyComponent.OnLevelChanged := MyTest.FirstTarget;
MyComponent.OnLevelChanged(nil, 1);

However, many .NET consumers will expect multicast events in your classes. To enable this in a Delphi for .NET class you simply use add and remove specifiers instead of read and write, like this:

  TMyComponent = class
strict private
FOnMultiChanged: TLevelChangedEvent;
public
property OnMultiChanged: TLevelChangedEvent add FOnMultiChanged remove FOnMultiChanged;
end;

The simplest solution is simply to reference a delegate field as before - the compiler will then implement proper add_Event and remove_Event methods for you. In some special cases you may want to implement your own logic in these routines - to do that you simply write and reference your own add_Event and remove_Event methods, like this

  TMyComponent = class
strict private
FOnCustomChanged: TLevelChangedEvent;
public
procedure add_OnCustomChanged(Value: TLevelChangedEvent);
procedure remove_OnCustomChanged(Value: TLevelChangedEvent);
property OnCustomChanged: TLevelChangedEvent add add_OnCustomChanged remove remove_OnCustomChanged;
end;

procedure TMyComponent.add_OnCustomChanged(Value: TLevelChangedEvent);
var
Inlist: Delegate;
begin
if Assigned(FOnCustomChanged) then
for Inlist in Delegate(@FOnCustomChanged).GetInvocationList do
if InList.Equals(Delegate(@Value)) then
Exit;
FOnCustomChanged := TLevelChangedEvent(Delegate.Combine(Delegate(@FOnCustomChanged), Delegate(@Value)));
end;

procedure TMyComponent.remove_OnCustomChanged(Value: TLevelChangedEvent);
begin
FOnCustomChanged := TLevelChangedEvent(Delegate.Remove(Delegate(@FOnCustomChanged), Delegate(@Value)));
end;

This example add-handler only allows unique delegate targets, ignoring any attempt to add the same object’s method more than once. Note the tricky-looking code with casts to Delegate and use of the @-operator. The Delegate casts are required to force the compiler to treat the procedure of object as a System.Delegate instance (which is an implementation detail from the compiler’s point of view). The @-operator is required to prevent the compiler from trying to call the event instead of evaluating its value.

Most WinForms events are multicast events - they support multiple methods as targets. To add or remove a method from a multi-cast event, you use the Include and Exclude intrinsic procedures:

  Include(MyComponent.OnMultiChanged, MyTest.FirstTarget);
Include(MyComponent.OnMultiChanged, MyTest.SecondTarget);
MyComponent.TriggerMulti(6);
Exclude(MyComponent.OnMultiChanged, MyTest.FirstTarget);

This corresponds directly to the += and -= operators that C# supports on events.


Tip: Use multi-cast add/remove events for WinForms code and components. Use single-cast read/write events for VCL for .NET code and components, unless you really need multi-cast behavior. "


Update/Note: If you need to implement an interface that includes a multi-cast event property - i.e. the interface has add_Event and remove_Event methods - you can get by by declaring an add/remove event property on the class, with the add and remove specifiers referencing a procedure of object field. This will force the compiler to generate the add_Event and remove_Event methods for you - just like it did in the third code block above.

Wednesday, September 26, 2007

DN4DP#19: .NET only: Unsafe code

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

Last time we looked at a new array syntax supported in .NET. This post covers the dangerous sounding concept of unsafe code.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Unsafe code

Delphi for .NET now supports unsafe code. Since unsafe code fails PEVerify checks, you first have to enable the {$UNSAFECODE ON} compiler directive, then you have to mark the method with the unsafe directive.

{$UNSAFECODE ON}
function Foo(const A: array of char): integer; unsafe;
var
P: PChar;
Fixed: GCHandle;
begin
Fixed := GCHandle.Alloc(A, GCHandleType.Pinned);
try
P := Pointer(Fixed.AddrOfPinnedObject);
Result := 0;
while P^ <> #0 do
begin
Result := Result + Ord(P^);
Inc(P);
end;
finally
Fixed.&Free;
end;
end;
procedure Test;
var
I: integer;
begin
I := Foo(New(array[] of char, ('A', 'B', 'C')));
Writeln(I);
end;


Tip: Delphi for .NET does not currently have a fixed keyword to pin managed objects in memory. Use GCHandle.Alloc from the System.Runtime.InteropServices namespace instead. "

Monday, September 24, 2007

DN4DP#18: .NET only: New array syntax

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

Last time we looked at multi-unit namespace support. Now we will jump to a new array syntax supported in .NET.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"New array syntax

While native Delphi supports both static and dynamic arrays, Delphi for .NET now also supports multi-dimensional, rectangular dynamic arrays. These differ from jagged array of arrays in that there is only a single, continuous block of memory allocated for the items in it, and the size of all dimensions can be set dynamically at runtime. This is mostly a performance and memory usage optimization, but it is also required to be able to interface with external code that uses them.

The syntax to declare a multi-dimensional dynamic array is array[,] with one comma for each extra dimension. To allocate a new array, use the New(array [dim1, dim2 ..] of TElement) syntax. To change the size of an existing array, use SetLength with one or more dimension parameters – this will preserve the contents of the array.

var
MyArray: array of integer;
JaggedArray: array of array of integer;
MyMatrix: array[,] of integer;
MyCube: array[,,] of integer;
begin
MyArray := New(array [4] of integer);
JaggedArray := New(array [3] of array of integer);
MyMatrix := New(array [3,3] of integer);
MyCube := New(array [2,2,2] of integer);
//...
SetLength(MyMatrix, 10, 20);
SetLength(MyCube, 10, 20, 30);
end;


Note: While it is possible to create new arrays using SetLength, the New syntax generates slightly smaller and more efficient code. And SetLength cannot currently (Delphi 2006) be used to create a new multi-dimensional [,] array.


There are two new ways to create a new initialized dynamic array from a list of elements. You can use the New statement and follow the array type with a parantesed list of elements. Or you can use a new TArrayType.Create constructor syntax with the elements as parameters – this syntax is also supported in Win32.

begin 
MyArray := New(array[] of integer, (1, 2, 3));
JaggedArray := New(array[] of array[] of integer,
(New(array[] of integer, (1, 2, 3)),
New(array[] of integer, (1, 2)),
New(array[] of integer, (1))));
MyMatrix := New(array[,] of integer, ((1,2,3), (4,5,6)));
MyCube := New(array[,,] of integer, (((1,2), (5,6)), ((3,4), (7,8))));
// ...
MyArray := TIntegerArray.Create(1, 2, 3);
JaggedArray := TJaggedArray.Create(
TIntegerArray.Create(1, 2, 3),
TIntegerArray.Create(1, 3),
TIntegerArray.Create(1));
end;

This way of initializing dynamic arrays inline is a great improvement of the old way of first allocating the array using SetLength and then explicitly setting the value of each indexed element. This is particularly useful when calling one of the many FCL methods that have array parameters.

unit NewArraySyntaxU;

interface

procedure Test;

implementation

type
TStaticIntegerArray = array[0..5] of integer;
TIntegerArray = array of integer;
TJaggedArray = array of TIntegerArray;
// New in D2005, .NET only
{$IFDEF CLR}
TIntegerMatrix = array[,] of integer;
TIntegerCube= array[,,] of integer;
{$ENDIF}

{$IFDEF CLR}
procedure InnerDumpArray(const Prefix: string; A: System.Array);
var
O: TObject;
begin
write(Prefix);
for O in A do
begin
if O is System.Array
then InnerDumpArray(#13#10' ', System.Array(O))
else Write(O, ', ');
end;
end;

procedure DumpArray(const Name: string; A: System.Array);
begin
write(Name, ' (Rank=', A.Rank, ')' );
InnerDumpArray(': ', A);
Writeln;
end;
{$ELSE}
procedure DumpArray(const Name: string; const A: array of integer); overload;
var
I: Integer;
begin
write(Name, ': ');
for I in A do
Write(I, ', ');
Writeln;
end;

procedure DumpArray(const Name: string; const A: TJaggedArray); overload;
var
I: Integer;
IA : TIntegerArray;
begin
writeln(Name, ': ');
for IA in A do
begin
write(' ');
for I in IA do
Write(I, ', ');
Writeln;
end;
end;
{$ENDIF}

procedure Test;
var
MyStatics: TStaticIntegerArray;
MyArray: TIntegerArray;
JaggedArray: TJaggedArray;
I: integer;
{$IFDEF CLR}
MyMatrix: TIntegerMatrix;
MyCube: TIntegerCube;
{$ENDIF}
begin
// In .NET static arrays are implicitly allocated by the compiler at runtime
// and all elements are initially cleared (0)
// In Win32, static arrays are stored on the stack and contain random values
MyStatics[1] := 42;
DumpArray('MyStatics', MyStatics);

{$IFDEF CLR}
// Use New syntax to create a new array
MyArray := New(array [4] of integer);
MyArray[0] := 13;
DumpArray('MyArray1', MyArray);

// To New up a truly jagged array, new up each dimension separately
JaggedArray := New(array [3] of array of integer);
for I := Low(JaggedArray) to High(JaggedArray) do
JaggedArray[I] := New(array [I+1] of integer);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 2] := 3;
DumpArray('JaggedArray1', JaggedArray);

// Use New(Type, dim1, dim2) to create a rectangular jagged array (array of arrays)
JaggedArray := New(TJaggedArray, 3, 2);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 1] := 3;
DumpArray('JaggedArray2', JaggedArray);

// New also supports initializing the array elements
MyArray := New(array[] of integer, (1, 2, 3));
DumpArray('MyArray2', MyArray);
JaggedArray := New(array[] of array[] of integer,
(New(array[] of integer, (1, 2, 3)),
New(array[] of integer, (1, 2)),
New(array[] of integer, (1))));
DumpArray('JaggedArray3', JaggedArray);
{$ENDIF}

// Use SetLength to change the size of an existing array
SetLength(MyArray, 1);
DumpArray('MyArray3', MyArray);

// You can allocate a rectangualar "jagged" array using a single SetLength call
SetLength(JaggedArray, 3, 3);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 2] := 3;
DumpArray('JaggedArray4', JaggedArray);

// To allocate a truly jagged array, use SetLength inside a loop
SetLength(JaggedArray, 3);
for I := 0 to 2 do
SetLength(JaggedArray[I], I+1);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 2] := 3;
DumpArray('JaggedArray5', JaggedArray);

// Finally you can use the TArray.Create syntax to initialize an array with elements
MyArray := TIntegerArray.Create(1, 2, 3); // New in Win32 since D7
DumpArray('MyArray4', MyArray);
{$IFDEF CLR}
JaggedArray := TJaggedArray.Create(
TIntegerArray.Create(1, 2, 3),
TIntegerArray.Create(1, 3),
TIntegerArray.Create(1));
DumpArray('JaggedArray6', JaggedArray);

// You can also New up rectangular multidim [,] arrays
MyMatrix := New(array [3,3] of integer);
MyMatrix[2,2] := 19;
DumpArray('MyMatrix1', MyMatrix);

MyCube := New(array [2,2,2] of integer);
MyCube[1,0,1] := 9;
DumpArray('MyCube1', MyCube);

// Note: for [,] arrays you cannot used the type alias in the New statement
// MyMatrix := New(TIntegerMatrix, ((1,2,3), (4,5,6)));
MyMatrix := New(array[,] of integer, ((1,2,3), (4,5,6)));
DumpArray('MyMatrix2', MyMatrix);
MyCube := New(array[,,] of integer, (((1,2), (5,6)), ((3,4), (7,8))));
DumpArray('MyCube2', MyCube);

// Note that SetLength does not work correctly for *uninitialized* [,] arrays in 2006
// SetLength does work for initialized [,] arrays
SetLength(MyMatrix, 1, 2);
DumpArray('MyMatrix3', MyMatrix);
SetLength(MyCube, 1, 2, 3);
DumpArray('MyCube3', MyCube);
{$ENDIF}

end;

end.

"

Saturday, September 22, 2007

DN4DP#17: .NET only: Multi-unit namespaces

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

The previous post covered platform differences for floating-point semantics. This time we'll look at namespace support.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Multi-unit namespaces

With the trinity of a logical unit concept, physical unit source .pas files and compiled .dcu files, Delphi has always had a very efficient and useful module concept. To address the hierarchal namespace support required in .NET while still being backwards compatible, Delphi 8 introduced the concept of dotted unit names, such as Borland.Vcl.SysUtils and Borland.Vcl.Classes - these unit names were mapped directly to .NET namespaces.

This was a step in the right direction, but Delphi 2005 extended this concept to allow multiple Delphi units to contribute to the same logical namespace. Now the namespace of a dotted unit name is everything up to the last dot.

So now both the SysUtils and Classes units reside in a single Borland.Vcl namespace. This allows the programmer to split his code into multiple physical units, while exposing the contained classes in a single logical namespace. This makes it easier and more convenient to write assemblies that can be used by other languages (such as C#).

unit MultiUnit.Namespaces.Unit2;
...
class procedure TBar2.Foo;
begin
Writeln(TBar2.ClassInfo.ToString, '.Foo');
end;

The code above writes the fully qualified namespace of the TBar2 type, and the output is MultiUnit.Namespaces.TBar2.Foo in this case.

unit MultiUnit.Namespaces.Unit1;

interface

type
TBar1 = class
class procedure Foo; static;
end;

procedure Test;

implementation

class procedure TBar1.Foo;
begin
{$IFDEF CLR}
Writeln(TBar1.ClassInfo.ToString, '.Foo');
{$ELSE}
Writeln(TBar1.ClassName, '.Foo');
{$ENDIF}
end;

procedure Test;
begin
TBar1.Foo;
end;

end.
unit MultiUnit.Namespaces.Unit2;

interface

type
TBar2 = class
class procedure Foo; static;
end;

implementation

class procedure TBar2.Foo;
begin
Writeln(TBar2.ClassInfo.ToString, '.Foo');
end;

end.
unit MultiUnit.Namespaces.Unit3;

interface

procedure Test;

implementation

uses
MultiUnit.Namespaces.Unit1,
MultiUnit.Namespaces.Unit2;

procedure Test;
begin
Writeln('Note that even though TBar1 and TBar2 are in separate Delphi units');
Writeln('they reside in the same namespace (MultiUnit.Namespaces)');
TBar1.Foo;
TBar2.Foo;
end;

end.

"

Friday, September 21, 2007

DN4DP#16: .NET only: Floating-point semantics

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

The previous post covered support for .NET Attributes. This time we will look at platform differences when it comes to floating-point operations.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Floating-point semantics

Native Delphi signals invalid floating-point operations by raising exceptions such as EZeroDivide and EOverflow. These semantics are preserved in Delphi for .NET by default. While the Win32 floating-point exception support is efficiently implemented directly in the processor hardware, the corresponding .NET support must be implemented explicitly in software by emitting the ckfinite IL instruction.

The .NET solution is a little slower, so if you have time-critical code that do not depend on exceptions being raised, you can speed it up a little by using the {$FINITEFLOAT OFF} compiler directive. In this mode, invalid operations will return special floating-point values like NaN (Not a Number), +Inf and -Inf (Infinity) instead of raising exceptions. To get the same semantics in Win32 code, you use the SetExceptionMask function from the Math unit.

{$IFDEF CLR}
{$FINITEFLOAT OFF}
{$ELSE}
Math.SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]);
{$ENDIF}
One := 0;
Two := 42;
Three := Two / One; // Returns +Inf, no exception raised

In .NET the generated IL code for the division statement is:

FloatingPointSemanticsU.pas.51: Three := Two / One; 
IL_0063: ldloc.1
IL_0064: ldloc.0
IL_0065: div
IL_0066: ckfinite
IL_0067: stloc.2

At run time the ckfinite IL instruction actually expands into seven x86 instructions, including CALLing a routine – doing this for every floating-point operation slows things down noticeably.


Tip: Unless you absolutely need floating-point exceptions, turn them off with {$FINITEFLOAT OFF}

unit FloatingPointSemanticsU;

interface

procedure Test;

implementation

uses
SysUtils, Math;

procedure TestNaNs;
var
One, Two, Three: Double;
begin
Writeln('Floating point exceptions off:');
{$IFDEF CLR}
{$FINITEFLOAT OFF}
{$ELSE}
Math.SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide,
exOverflow, exUnderflow, exPrecision]);
{$ENDIF}
One := 0;
Two := 42;
Three := Two / One; // Returns +Inf, no exception raised
Writeln(Two:1:1 , ' / ', One:1:1, ' = ', Three:1:1);
One := 0;
Two := -42;
Three := Two / One; // Returns -Inf, no exception raised
Writeln(Two:1:1 , ' / ', One:1:1, ' = ', Three:1:1);
One := 0;
Two := 0;
Three := Two / One; // Returns NaN, no exception raised
Writeln(Two:1:1 , ' / ', One:1:1, ' = ', Three:1:1);
end;

procedure TestExceptions;
var
One, Two, Three: Double;
begin
Writeln('Floating point exceptions on :');
{$IFDEF CLR}
{$FINITEFLOAT ON}
{$ELSE}
Math.SetExceptionMask([exDenormalized, exOverflow, exUnderflow, exPrecision]);
{$ENDIF}
One := 0;
Two := 42;
try
Write(Two:1:1 , ' / ', One:1:1, ' = ');
Three := Two / One;
Writeln(Three:1:1);
except
on E:EMathError do
writeln(E.ClassName, ': ', E.Message);
end;
One := 0;
Two := -42;
try
Write(Two:1:1 , ' / ', One:1:1, ' = ');
Three := Two / One;
Writeln(Three:1:1);
except
on E:EMathError do
writeln(E.ClassName, ': ', E.Message);
end;
One := 0;
Two := 0;
try
Write(Two:1:1 , ' / ', One:1:1, ' = ');
Three := Two / One;
Writeln(Three:1:1);
except
on E:EMathError do
writeln(E.ClassName, ': ', E.Message);
end;
end;

procedure Test;
begin
TestNaNs;
writeln;
TestExceptions;
end;

end.

"

Thursday, September 20, 2007

DN4DP#15: .NET only: Attributes support

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

The previous post covered support for .NET Boxing operations. We continue with the .NET specific topic of Attributes support.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Attributes support

Any proper .NET language must support associating attributes to classes and members, and Delphi is no exception. The attribute syntax closely resembles C#. The attribute class name (with or without the Attribute suffix) is placed inside square brackets prior to the element you want to apply it to. The attribute name may be followed by parens with constructor parameters and property assignments. Normally, the compiler will figure out what element the attribute belongs to by its context, but you can prefix the attribute name with a target indicator and colon, such as assembly:.

type
[MyCustom(Age=42, Name='Frank Borland')]
TMyObject = class

[MyCustomAttribute('Ida Emilie', 10)]
procedure Foo;
end;

[assembly:MyCustomAttribute('Thea Ulrikke', 3)]

Sample code

unit AttributesSupportU;

interface

type
[AttributeUsage(AttributeTargets.All)]
MyCustomAttribute = class(Attribute)
public
Name: string;
Age: Integer;
constructor Create; overload;
constructor Create(AName: string); overload;
constructor Create(AName: string; AAge: integer); overload;
function ToString: string; override;
end;
type
[MyCustom(Age=42, Name='Frank Borland')]
TMyObject = class
[MyCustomAttribute('Ida Emilie', 10)]
procedure Foo;
end;

[assembly:MyCustomAttribute('Thea Ulrikke', 3)]
procedure Test;

implementation

uses
SysUtils,
System.Reflection;

{ TMyCustomAttribute }

constructor MyCustomAttribute.Create;
begin
Create('', 0);
end;

constructor MyCustomAttribute.Create(AName: string);
begin
Create(AName, 0);
end;

constructor MyCustomAttribute.Create(AName: string; AAge: integer);
begin
inherited Create;
Name := AName;
Age := AAge;
end;

function MyCustomAttribute.ToString: string;
begin
Result := Format('%s, Name=%s, Age=%d', [inherited ToString, Name, Age]);
end;

{ TMyObject }

procedure TMyObject.Foo;
begin
Writeln('TMyObject.Foo');
end;

procedure Test;
var
T: System.Type;
O: TObject;
M: MemberInfo;
A: Assembly;
begin
T := TMyObject.ClassInfo;
Writeln(T.ToString);
for O in T.GetCustomAttributes(False) do
writeln(' ', O.ToString);
Writeln;
M := T.GetMethod('Foo');
Writeln(M.ToString);
for O in M.GetCustomAttributes(False) do
writeln(' ', O.ToString);
Writeln;
A := T.Assembly;
Writeln(A.ToString);
for O in A.GetCustomAttributes(False) do
writeln(' ', O.ToString);
end;

end.

"

Tuesday, September 18, 2007

DN4DP#14: .NET platform support: Boxing

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

The previous post covered overloaded default array properties. The next few posts will be .NET specific - starting out with support for Boxing operations.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

".NET platform support

Custom features for a managed environment

Most of the language extensions we have discussed so far have been general in nature and applicable to both the .NET and Win32 platforms. In addition, Borland [now CodeGear - Ed.] has added new language features that map directly to intrinsics of the .NET platform. These make it easier for you to integrate with the FCL, external assemblies, the CLR and so on.

Boxing

As you know from Chapter 2 ["Just In Time Compilation" - Ed.], boxing is the process of converting a value type instance into a System.Object compatible reference, copying the value to the garbage-collected heap. By default in Delphi, boxing is an explicit operation involving a cast expression from the value type to System.Object (or the alias TObject).

While explicit boxing helps you identify spots where potentially expensive copy operations are going on, it can become bothersome in the long run. To enable implicit boxing operations (just like in C#), you can use the {$AUTOBOX ON} compiler directive.

Finally, the syntax to perform unboxing is identical with C# - just cast the TObject reference to the required value type. You can use the is operator to verify the actual value type held by the object reference.

procedure Test;
var
Collection: ArrayList;
O: TObject;
begin
Collection := ArrayList.Create;

{$AUTOBOX OFF}
Collection.Add(TObject(42));

{$AUTOBOX ON}
Collection.Add(84);

for O in Collection do
writeln(Byte(O));
end;


Note: When boxing integral constants, the compiler will box them as the smallest integral type that can hold it. If you want to box a constant as a specific type, you need to cast it or store it in a correctly typed variable first, like this Collection.Add(Integer(84)).

Monday, September 17, 2007

DN4DP#13: Overloaded default array properties

It's been a while now, but this post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

What a good time to continue this series just after the major Delphi for .NET 2.0 launch, CodeGear RAD Studio 2007!

The previous post covered the exotic topic of Record Helpers. Today we'll look at another exotic-ish topic - overloaded default array properties.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Overloaded default array properties

A class or component can have an array property that is declared as default. This mechanism has now been extended to allow multiple overloaded default array properties - as long as the number or types of indexer parameters are different.

type
TMyObject = class
public
property Items[Index: integer]: string read GetItems write SetItems; default;
property Items[const Name: string]: string read GetNamedItems write SetNamedItems; default;
end;

This means that you can use the array indexing syntax on the object instance - effectively overloading the array subscript operator [].

procedure Test; 
var
MyObject: TMyObject;
begin
MyObject := TMyObject.Create;
MyObject[42] := 'The Answer';
MyObject['Bar'] := 'Yes';
end;

"

Friday, September 07, 2007

RAD Studio 2007 launch in Oslo, Friday Sept 14th

The largest Delphi meetup.com group in the world (121 members and counting!), Oslo Delphi Club,  is arranging a launch event for the new Delphi and C++Builder version called RAD Studio 2007. This is the version that, amongst other things,  introduces full support for .NET 2.0 and Generics in Delphi for .NET (as shown here).

If you're in or close to Oslo, Norway make sure you're not missing this event! If you are (or want to become) a ODC member, RSVP here. You should also sign up for the event itself here.

Thanks to the new Oslo Delphi Club organizer, Kjetil Korsveien of Alfacode (the CodeGear product distributor in Norway) for organizing and hosting this event!

See you there!



Copyright © 2004-2007 by Hallvard Vassbotn