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 .NET and Win32 constructors. This is the final post in this long running series and it covers the main differences between Delphi and C#.
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.
"Delphi vs C#
Very similar, but different in the details
If you are faced with the task for porting code from native Delphi to C#, or to convert C# code snippets to Delphi, or to make in informed decision of what language to use, it is useful to know the unique strengths and features of each language. In this section we quickly iterate over the highlights of each language and include some tips on porting between them.
Note that we compare Delphi for .NET to C# version 1.0, not version 2.0. It wouldn’t be fair to compare a .NET 1.1 product like Delphi for .NET 2006 to C# 2.0 with its support for generics, nullable types and iterators.
Delphi language highlights
Of the features that we have already covered, class helpers, unmanaged exports and virtual library interfaces are unique features that Delphi has. In one sense class helpers are very similar to the extension methods of the upcoming C# 3.0 standard to support the Linq (Language integrated query) technology. The main difference is that class helpers cannot help interfaces (at least not yet) and class helpers are more structured.
The following table lists the most important Delphi language features that C# does not have and what their alternative is when porting code.
Delphi feature | Comment | C# alternatives |
class helpers | Platform-leveling compiler magic | Explicit static methods C# 3.0 extension methods |
Unmanaged exports | a.k.a. reverse P/Invoke | Use C++, hacks |
Virtual Library Interfaces | a.k.a. dynamic P/Invoke | Use unmanaged C++, hacks |
sets | Limited to ordinal types with <= 256 elements | enum flags, BitArray, int bit-fiddling |
class of references | Meta classes | System.Type |
virtual class methods | Meta class polymorphism | System.Type, reflection |
virtual constructors | Class factories | Activator, reflection |
Type-less var and out parameters | Poor-man’s generics | C# 2.0 Generics, System.Object function |
type aliases, typed types | Logical vs. actual types | Explicit typing |
Default parameters | Simpler than overloading | Overloading |
resourcestrings | Simplified internationalization | Resources and ResourceManager.GetString |
Named constructors | Simulated using overloading | Overloading |
message methods | Dispatching windows messages | WndProc switch |
Variants | One-type fits all | System.Object boxing |
Global routines | Non-OOP code | Static methods of a class |
Global variables | Non-OOP data | Static fields of a class |
Named array properties | Multiple array properties | Overloaded this indexer Nested class with this indexer |
Local (nested) procedures | Implementation hiding, automatic access to outer variables | private method, anonymous method (C# 2.0) |
variant records (case) | Structure overlaying (union) | [StructLayout(LayoutKind.Explicit)], [FieldOffset()] |
Text files, Writeln, etc | Easy input/output | Console and Stream classes |
Supports Win32, Linux | Cross-platform capabilities | Use C/C++, Mono for Linux |
One difference that is important to be aware of is that hard-casts and safe-casts have opposite syntax in Delphi and C#. The safe exception-raising cast is (O as TargetType) in Delphi and (TargetType)O in C#. The nil/null-returning cast is TargetType(O) in Delphi and (O as TargetType) in C#.
C# language highlights
The following C# 1.0 features are not directly available in Delphi, but have alternative ways of achieving the same goal.
C# feature | Comment | Delphi alternatives |
lock | Thread synchronization | Monitor.Enter(O); try finally Monitor.Exit(O); end; |
fixed |
Garbage collection object pinning |
H := GCHandle.Alloc(..) try P := H.AddrOfPinnedObject; finally H.&Free; end; |
using |
Deterministic releasing of unmanaged resources |
O := TMy.Create; try finally O.Free; end; |
C# destructor ~ClassName |
Garbage collection deallocate notification |
override Finalize method |
stackalloc |
Unsafe code temporary allocations |
GCHandle.Alloc dynamic array |
checked/ unchecked |
Integer arithmetic overflow checking |
{$OVERFLOWCHECKS ON/OFF} {$Q+/-} |
readonly field |
Read-only fields initialized in a constructor |
const, read-only property, normal field |
return |
Set function result and return to caller |
Result := O; Exit; |
volatile field |
May be modified outside current thread |
Explicit locks, Thread.VolatileRead/Write |
internal access |
Per-assembly cross-class implementation details |
public, protected with cracker-cast |
ternary ? : operator |
Inline test and return result |
if .. then .. else, IfThen routines |
switch (string) |
Multi-case testing of strings |
nested if..then..else, TStringList.IndexOf |
In Delphi an overridden Destroy destructor maps to an implementation of IDisposable, while in C# you must implement IDisposable explicitly. In C# a ~ClassName destructor maps to an overridden Object.Finalize method, while in Delphi Finalize must be overridden manually. In most cases, application-level code needs to implement IDisposable, but should not override Finalize – that should be left to low-level leaf-classes in the FCL.
"