|
|
An (incomplete) comparison of Delphi and C# features
Delphi and C# are both rich and powerful object oriented programming
languages. As a reminder a short recapitulation of what that stands for : Objects are a grouping of data and methods (functions) working
on that data. Objects are based on classes. A class is a declaration of the data
and the code of the methods. A class will create objects using constructor
methods.
An real object oriented language supports encapsulation,
inheritance and polymorphism. Encapsulation hides everything inside an object
except what you explicitly want to show to the outer world. Inheritance lets you
re-use a class to create a derived class with added functionality. Polymorphism
lets you re-use a class and alter its exposed behavior.
Delphi has a great support for object orientation but you can write a plain
Pascal program without using any object at all. In C# everything is an object,
including the application itself. Running a C# program means creating an object
and executing the object's main method. C# does not know how to handle any "loose"
procedures or constants either, everything has to belong to a class.
The Delphi-VCL couple shares a lot with the C#-.NET couple which makes a
comparison on items possible. This list is by far incomplete, it is just a
road-sketch from a Delphi perspective:
Both have
-
Inheritance
Both Delphi and C# support single inheritance, a class can have only one
base class.
-
Polymorphism
You can override a method in Delphi or C# and
so change the behavior of a descendent class, provided the method is marked as virtual
in the base class. In the descendent class the method
should be marked using the override directive. The inherited code
will be available in the overriden method. To hide the inherited method
Delphi uses the reintroduce directive, which is named new in
C#. Delphi dynamic methods follow the same rules as virtual,
the only differences are in the implementation of the actual object's method
table.
In C# you can end the possibility to override any methods in further
descendents of the class by marking the class as sealed. This is a
nice way to publish a class without the uncertainty that it's behavior will
be broken.
-
Class methods operate on the class itself and don't need an object of
that class to be executed. Typical class methods are constructors, named
constructor in Delphi. In C# the constructor is a method with the same
name as the class. The names of other class methods are prefixed with class
in Delphi and with static in C#. The C# keyword static does also have
the
same meaning as static in Delphi, you cannot override a C# static method. But you can
override a class method in Delphi.
-
In a hierarchy of objects every derived class class can introduce a new
constructor. Which constructors actually get executed differs between the
two languages, in Delphi each constructor has to be called explicitly.
Besides the constructors Delphi's tObject base class has a method
AfterConstruction, which will be executed after the last constructor has
finished. So there are many places where a Delphi object can be initialized.
In C# the constructor of the ultimate base class System.object always will be
executed first, the execution of a constructor of any other base class has to
be explicitly asked for in the declaration of the constructor as ":
base." There is no AfterConstruction in C#, everything has to be (and
can be) done in the constructors themselves.
Type
tMyClass = class(tObject)
constructor createEx;
end;
tMyClass2 = class(tMyClass)
constructor createEx; override;
procedure AfterConstruction override;
end;
constructor tMyClass.CreateEx;
begin
...
end;
constructor tMyClass2.CreateEx;
begin
inherited CreateEx;
...
end;
procedure tMyClass2.AfterConstruction
begin
inherited;
// Initialization
end;
public
class MyClass
{
public MyClass(int
anInt)
{...}
}
public class
MyClass2 : MyClass
{
public MyClass2(int
anInt) : base(anInt)
{
...
//
Initialization
}
}
-
Object finalization
The lifespan of an object is very different in Delphi and .NET. In Delphi
you have to explicitly free an object by making a call to (tObject.) Free
which destroys the object by calling its destructor. Failing to do so leads
to resource leaks. Resources like memory, forms, database handles and
everything else your object has been using. In the VCL many classes are
based on tComponent, objects of this class can own other objects. When a
tComponent object is freed it will free all objects it owns, the class takes
a lot of pain out of object management in Delphi. Besides
overriding the destructor itself, tObject has a nice BeforeDestruction method
which will fire before the first destructor will execute. This method is a
good place to free resources.
In .NET objects can only be destroyed by the garbage collector.
Inside the garbage collector there is a separate finalizer thread running which will
call the finalizer on all objects who are no longer referenced by any code.
Finalizers can be compared to the destructors in Delphi but it is strongly
advised not to use or rely on them. The main reason for this is that it is completely unknown
when and in what order the objects will be destroyed. The garbage collector can be manipulated by the
application, the actual
order in which the objects will be destroyed cannot. Any
resources used by the objects, like database connections or windows handles will not be freed until the garbage collector
has decided it time to destroy the object. Which could be hours after the
application itself terminated. As a solution .NET has the Idisposable
interface. Any class which implements dispose, the only method of
this interface, can free up its resources in the dispose implementation. Many classes
in the framework implement the interface and calls to Dispose pop up
everywhere in code generated by VS.NET wizards and in the .NET
documentation.
For an elaborate discussion on the differences between object
finalization in C# and Delphi and how Delphi for .NET bridges the gap there
is a very good
article by Brian Long on the Borland
developer network site.
-
In both languages a method can be declared several times as long as every
declaration has a different signature. In Delphi an overloaded method
has to be marked with the keyword overload;
procedure MyMethod; overload;
procedure MyMethod(MyParam : integer); overload; public MyMethod()
public MyMethod(int
MyMethod) Constructors
can be overloaded as well. constructor Create;
overload;
constructor Create(MyInitParam : integer); overload; public MyClass()
public MyClass(int
MyInitParam)
-
An interface is like a class with only abstract members and is in both
languages an integral part of the language. In both languages a class
can implement any number of interfaces, the interface list is part of the
class declaration. The syntax in C# is slightly different then in
Delphi.
tMyClass = class(tYourClass,
YourInterface1, YourInterface2)
MyClass = class : YourClass, YourInterface, YourInterface2
-
To the user of the object a property behaves like any other field. In the
actual implementation reading or writing the property will be done by an
internal method. In this method the value can be checked or calculated. By
only implementing only the getter or only the setter the property will be read
or write only. In this example both the getter and the setter guard the
property against a value less then 0.
type MyClass = class(tObject)
private
fMyProp : integer;
function GetMyProp: integer;
procedure SetMyProp(const Value: integer);
public
property MyProp : integer read GetMyProp write SetMyProp;
end;
function MyClass.GetMyProp: integer;
begin
result:= max(fMyProp,0);
end;
procedure MyClass.SetMyProp(const Value: integer);
begin
if Value >= 0 then
fMyProp:= Value;
end;
In C# the value passed in the property setter is named value.
public class
MyClass
{
private
int myProp = 0;
public
int MyProp
{
get
{ return Math.Max(myProp, 0); }
set
{ if (value > 0)
myProp = value; }
}
}
-
Many Delphi components have notification properties. These will be
notified when a specific event, like a click on the button, is fired. A method can be assigned to
this notification, this method will be executed when the event is fired. In Delphi only one particular notification handling method
can be assigned to the event-handler.
An event-handler in C# is based on delegate objects. Delegate objects
hold the method to be executed when the click on the button does fire. The
method is passed to the constructor of the delegate. The big difference with
Delphi is
that C# event handlers are multicast, they manage a dynamic collection
of delegate objects and fire notifications to all the collection's
members.
procedure MyEventHandler(sender : object)
begin
ShowMessage('You clicked the button');
end;
MyButton.OnClick:= MyEventHandler;
private
void MyClick1(object
sender, System.EventArgs e)
{
MessageBox.Show("You
clicked the button");
}
private void
MyClick2(object sender, System.EventArgs e)
{
MessageBox.Show("And I'll
tell you twice");
}
MyButton.Click += new
System.EventHandler(MyClick1);
MyButton.Click += new
System.EventHandler(MyClick2);
-
The VCL has several classes which can be used as a base class for a
collection. The namespace System.collections is a part of the .NET framework
classes, it is a treasure-chest of classes and interfaces. The methods of a collection are
defined in the IEnumerator interface, any class which implements this interface can be
used as a collection. All collection classes in the FCL classes do implement
IEnumerator, your own
classes can do this as well. Items in such a collection can be enumerated
with the foreach statement.
-
Runtime Type Information
A Delphi published property has RTTI, and can be queried on type
information at runtime. In C# the type information of any object is
available at runtime through the GetType() method of System.object, the base class
of all .NET classes. This is named reflection in .NET.
-
Both languages use exceptions to handle errors. In Delphi an exception is
said to be raised, in C# an Exception is said to be thrown. Both languages support the
finally
clause for cleanup code. In C# finally can be combined with catching an
exception, to achieve this in Delphi two try's have to be nested
Try
Try
raise MyException.Create('Error occurred in
Delphi');
except
on E: MyException
end;
finally
CleanUp;
end;
try
{
throw
New MyException("Error occurred in C#");
}
catch(MyException)
{
HandleMyException();
}
finally
{
CleanUp();
}
Single rooted classes
All classes in both languages stem from one single class, what
tObject is to Delphi is System.object to C#.
To work with interfaced
objects Delphi follows two tracks. In a later version of Delphi tInterfaced
object was added to the VCL, a base class which implements the Iunknown
interface and is refcounted. Since then tInterfacedObject
has served as a base class for many components. Many classical COM interfaces
are still implemented using tObject. This situation inevitably complicates
the implementation of interfaced objects. Refcounting can be a problem with Delphi
interfaced objects, the
object will not be freed until the refcount reaches zero.
A .NET class can implement
any interface, without doing any refcounting. All objects
are being watched by the garbage collector
(gc), as soon as an object is no longer referenced by anyone the gc will
notice and clean up the object. This is the point where a gc really pays
off.
-
Extendable class library
Both languages use a large class library, what the VCL is to Delphi is
the framework class library, also known as the .net FCL, to C#.
The VCL was primarily aimed at encapsulating Windows complexity. Over the
years an enormous amount of components built on the VCL has seen the light.
They are provided by Borland and by many 3d party developers, together they
provide solutions for almost every (in-) conceivable problem.
.NET was
primarily aimed at bringing internet functionality to every corner of the
development process. When your are interested in creating Windows forms with
nice controls the VCL is still a clear winner. But the FCL is a library on which you
can build, many 3d party Delphi component designers now supply .NET versions
of their controls.
C# - .NET has
-
Every object created in Delphi has to be freed again, a large part of
this burden is taken care of by the owner of a tComponent descendant. When a
tComponent is freed it will first free all tComponents owned by it.
In C# the freeing of objects is done by the .net garbage collector. Every
object is being watched by the runtime system. The garbage collector,
working continuously in the background, will free all objects which are no
longer referenced by any piece of running code.
-
Type safety and custom type conversion
Type safety in C# assures that a variable is either of the declared type
or is null. You can try any typecast in C# but both compiler and runtime
system will perform extensive checks. An invalid typecast will throw an
exception. A more gentle approach is the as keyword, also known in
Delphi. The nice thing about as in C# is that a check on the type is
performed. An invalid type will result in a null (nil) value.
MyType Myvar = Avar as MyType;
if (MyVar == null)
MessageBox.Show("This is not a MyType...");
As an extra option C# offers custom
type conversions. This offers the possibility to explicitly program what
will happen to (the members of) an object when it is typecast from one type
to another. In Inside C# Tom Archer gives a very
clear example. He takes two temperature types, Celsius and Fahrenheit.
Typecasting a Celsius variable to a Fahrenheit variable will handle the
conversion of the value on the fly.
Celcius c = new
Celsius(0F);
Fahrenheit f = (Fahrenheit)c;
The variable f will now hold a value of 32 (degrees Fahrenheit)
-
A static class has only constant properties and static (class) methods.
No objects will be created of a static class. It can have a
static constructor to initialize the class, this will be executed when the
program loads. A static class is a good place to house constants which will
be used by several other classes.
class SharpGuids
{
/* Guid as string */
public const string intfguid = "E03D715B-A13F-4cff-92F1-0319ADB3DE5F";
public static readonly System.Guid idintf;
/* Static constructor to init the Guid based on the string */
static SharpGuids()
{
idintf = new System.Guid(intfguid);
}
}
-
Any object in C# can be indexed if it was an array. Any resulting type
and any type of the indexing value can be implemented by the writer of a
class. int cp = this["MyIndexer"];
MyObject o = ThatObject[12];
System.DateTime someTime = ThatObject[System.DateTime.Now];
An indexer is implemented by a writing an indexed property
on this, the object itself.
public class
MyClass
{
public
object this[string
myIndex]
{
get
{return MyMethod(myIndex);}
}
}
-
Classes and methods can have metadata, properties which are of importance
to the runtime environment. For an interface involved in COM it is important
to know it's identifying GUID, for a method involved in a dispinterface it
is important to know it's dispId, for a method involved in COM+ it is
important to know the transaction characteristics of the methods. Delphi
uses an attribute-like construction to bind a GUID to an interface :
IFileZapper = interface(IDispatch)
['{2E2FC5E0-5C0E-4C4F-8CC1-D9F6C5A92BA6}']
C# uses attributes for all meta data.
[Guid(SharpGuids.intfguid), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IamSharp
{
[DispId(1)]
void SayHi(string Anything);
[DispId(2)]
void SetOpacTo(int Percent);
}
Attributes in C# are objects
themselves and can be based on user defined classes. Which give you as a
developer a lot of power in defining and using your own custom metadata.
As all variables in .NET are objects, performing operations on them boils
down to making methods calls. Methods can be overloaded,
in .NET this is also possible for many of the basic operators like +, -,
<, etc. This can be very useful in your own classes. Let's take
an invoice class in which the + operator is overloaded
public class
Invoice
{
public double
Total;
public static
Invoice operator+ (Invoice invoice1, Invoice
invoice2)
{
Invoice NewInvoice = new
Invoice();
NewInvoice.Total = invoice1.Total +
invoice2.Total;
return
NewInvoice;
}
}
The method operator+ takes two Invoice objects as a
parameter, constructs a new invoice and sets it's properties. After this
declaration you can code:
Invoice anInvoice = new
Invoice();
Invoice anotherInvoice = new Invoice();
anInvoice.Total = 1200;
anotherInvoice.Total = 2500;
textBox1.Text = "Total amount is " + (anInvoice +
anotherInvoice).Total.ToString();
The + operator applied on two Invoice object will result in another
Invoice object with a total amount of 3700. The + operator applied on
objects of any other type will result in a default addition, the +
operator is overloaded for the Invoice class.
Where are we ?
C# and .NET can very well be compared to Delphi and the VCL. The fact that
the first is a clean start based on yesterdays lessons and today's needs make
it a very tempting development environment. Working with C# in Visual Studio.net
has been a very pleasing experience to me, it shows a stable and powerful
implementation of all ideas and will keep the pleasure in programming for quite
some time.
What's next ?
|