C# for Delphi developers

C# for Delphi developers1.0 022202

Foreword

This document is intended to be a reference for those Delphi programmers who want to learn C#. It explains how to translate your existing knowledge to C#. I hope you could forgive my mistakes, I'm not an english speaker and you are invited to send me any bugs or incorrect information you find at studio@qadram.com, I will fix it as soon as possible.

Introduction

C# is a component-oriented language, in the same way Delphi and C++Builder are, but it goes a bit beyond this concept. This is not a C# manual, after reading this manual, you must buy some C# book to learn all the new features C# have and Delphi not. Some descriptions and differences are explained “roughly”, is just an introduction to C# for Delphi Developers, it's not an in-depth C# manual.


Some things this manual doesn't cover

  • How to compile or execute C# programs
  • Attributes
  • Interfaces
  • Nested Classes
  • Versioning
  • C# language syntax
  • Conversions
  • Arrays
  • Indexers
  • Enumerators
  • User-defined Conversions
  • Operator Overloading
  • COM
  • .NET Framework
  • XML

Is this manual for you?

If you are a Delphi developer who wants to know how to translate your knowledge to C#, this is the first document you might want to read. It introduces you in the C# by explaining how to do the things you already do with Delphi. This manual is only about the language C#, not the .NET platform nor the Visual Studio .NET IDE, nor components.

About the Author

José León currently works as web developer for a company in Spain, if you want to know more about him, just go to http://www.qadram.com

QuickStart

Look at this source code:

using System;
class Hello
{
    public static void Main(string[] args)
    {
        Console.WriteLine(“Hello, from C#”);
        for (int arg=0; arg
 
 

  
  
        {
            Console.WriteLine(“Arg {0}:{1}”, arg, args[arg]);
        }
    }
}

When the program loads, the Main function is executed, it writes a string to the console and iterates through the parameters to dump the number of parameters and its value.

Main differences:

  • Using instead of Uses to add external functionallity
  • There's no code outside a class, there are no global functions in C#
  • The implementation of a method is tied to its declaration

DataTypes

The following table shows the correlation between Delphi data types and C# data types:

Delphi

C#

byte

byte

shortint

sbyte

smallint

short

word

ushort

integer

int

cardinal, longword

uint

int64

long

ulong

single

float

double

double

currency

decimal

string

string

char

char

boolean

bool

The main diference between Delphi datatypes and C# datatypes is not its name, in C#, datatypes are derived from the base class object, so it's possible to write sentences like this:

Console.WriteLine(“Value is:{0}”, 3.ToString());

Which is not possible in Delphi, this technique is called boxing.

Exception Handling

I can't see many differences between C# Exception Handling and Delphi's one, it's another syntax but nothing more:

Try .. Except .. End

try
{
    int j=22 / Zero;
}
catch (Exception e)
{
    Console.WriteLine(“Exception “+e.Message);
}

If you want to catch different kind of exceptions you just only need to write several catch blocks. The ancestor of all exceptions is the Exception class, the same as Delphi.

try
{
    int j=22 / Zero;
}
catch (DivideByZeroException e)
{
    Console.WriteLine(“Exception “+e.Message);
}
catch (Exception e)
{
    Console.WriteLine(“Exception “+e.Message);
}

The catch block that catches the DivideByZeroException is the more specific match.

Try..Finally..End

C# also supports Try..Finally using similar syntax to Delphi

FileStream f = new FileStream(“data.txt”, FileMode.Open);
try
{
    //perform operations with the file 
}
finally
{
    f.Close();
}

There's another option which is equivalent to this Delphi code:

try
    try
        except
            // manage here your exceptions
        end
finally
    // perform some cleanup here
end;

In C# you can write:

try
{
}
catch (Exception e)
{
    Console.WriteLine(“Exception “+e.Message);
}
finally
{
    // perform some cleanup here
}

In Delphi, you should destroy any object you create or free any resource you allocate. The primary use of exception handling is to ensure everything is destroyed and freed. Because C# has Garbage Collection, you don't need to worry about this, you will use exception handling only to ensure the proper finalization of operations, so this improves the efficiency of the code.

Classes

C# classes work like Delphi, you don't need to learn new OO concepts, the main difference, at source code level, is the code is written just below the prototype of a function.

class MyClass
{
    public int myFunction()
    {
        return(5);
    }
}

Also you must specify the visibility of each member of a class (field, method, etc), because they are private by default. For example:

class MyInteger
{
    int integer=0;
}

If you create an instance of MyInteger and you use the integer field of the MyInteger class, this will produce an error because is private:

class TestPrivate
{
    public static void Main()
    {
        MyInteger mi = new MyInteger();
        Console.WriteLine(“{0}”, mi.integer);
    }
}

This will produce an error, you must specify public when declaring a member visible to other classes.

In the previous sample, you don't need to destroy the mi variable, the garbage collector does it for you. In Delphi you can use self inside a class to refer to the current instance, in C# use this instead self.

Constructors

Constructors are not labeled with the constructor reserved word, in C#, constructors are member functions with the same name of the class:

class Point
{
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    public int x;
    public int y;
}

ref and out Parameters

To get several values at once from a function, in Delphi you use var, to indicate a reference parameter, the function can modify the parameter and the caller will know the new value.

There are two ways to do this in C#, you can use ref or out. The difference is the compiler won't allow to pass uninitialized variables as ref parameters.

class Point
{
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    public void GetPoint(ref int x, ref int y)
    {
        x = this.x;
        y = this.y;
    }     
    int x;
    int y;
}

To call the GetPoint member of the Point class, you should call it as:

int x=0;
int y=0;
Apoint.GetPoint(ref x, ref y);

If you forget to initialize the parameters, the compiler will generate an error, if you don't want this, just change ref by out.

Overloading

To declare several methods in a class with the same name and different parameter list, you don't need to mark them as overload. The compiler chooses which to call by matching the parameters in the call to the parameters declared for the function.

Inheritance

To create derived classes from base classes, you can do it this way:

class BaseClass
{
    public BaseClass()
    {
        //Do something
    }
}
class DerivedClass: BaseClass
{
    public DerivedClass()
    {
    }
}

One big difference is constructors cannot be inherited, so you must ask, How can I call the base class constructor?. In Delphi, you can use the inherited reserved word, in C# you use the base syntax. Look at the following sample:

class BaseClass
{
    public BaseClass(string param, int value)
    {
        this.param = param;
        this.value = value;               
    }
    string param;
    int value;
}
class DerivedClass: BaseClass
{
    public DerivedClass(string param,int value): base (param, value)
    {
    }
}

Using the base syntax, you call the constructor from the base class with the parameters of the constructor of the derived class, if you omit the base syntax, the base class constructor will be called with no parameters.

Virtual Functions

To declare virtual functions and override them in derived classes, the syntax is very similar to Delphi, to allow a function be overritten in a derived class, you must declare it as virtual, and in a derived class to override a function from the base class, you must declare it as override.

class BaseClass
{
    public BaseClass(string param, int value)
    {
        this.param = param;
        this.value = value;               
    }
    virtual public getValue()
    {
        return(value);
    }
    string param;
    int value;
}
class DerivedClass: BaseClass
{
    public DerivedClass(string param,int value): base (param, value)
    {
    }
    override public getValue()
    {
        return(value*5);
    }
}

Abstract Classes

If you want to force a derived class to override one or more members, you can declare them as abstract, this means you don't need to create an implementation for that member in the base class and you are forcing a derived class to implement it.

There are several differences between C# and Delphi, in Delphi, you can declare an abstract member, but you are not forced to implement it on a derived class, you will get just a warning, and in runtime, if you call this member, you will get an abstract error.

In C# you are forced to implement an abstract member in a derived class, if you don't do it, the program won't compile. Also, if you declare an abstract member on a class, you must declare the class as abstract and you can't create instances of that class because it contains missing functionality. Look at this example:

abstract class BaseClass
{
    public BaseClass(string param, int value)
    {
        this.param = param;
        this.value = value;               
    }
    abstract public getValue();
    string param;
    int value;
}
class DerivedClass: BaseClass
{
    public DerivedClass(string param,int value): base (param, value)
    {
    }
    override public getValue()
    {
        return(value*5);
    }
}

You cannot create instances of BaseClass, and if you don't override the member getValue, the program won't compile.

Sealed Classes

There's another feature I think Delphi doesn't have, is called Sealed Classes, and is a way to prevent derivation from a base class.

This program will produce an error:

sealed class MyClass
{
    MyClass()
    {
    }
}
class derived: MyClass
{
}

Class Member Accesibility

In Delphi, you can declare several classes in the same unit, let's supose class A and class B, if you declare a private member on class A, you can access that member inside class B.

TClassA=class
private
    a: integer;
end;
TClassB=class
public
    procedure hello;
end;
implementation
procedure TClassB.hello;
var
    classA:TClassA;
begin
    classA:=TClassA.create;
    classA.a:=5;
end;

In C# this is not the default behaviour, to achieve this behaviour, you must declare the member as internal.

public class ClassA
{
   internal int a;
}
public class ClassB
{
   public void hello()
   {
       ClassA ca=new ClassA();
       ca.a=5;
    }
}

In C# there's a concept called assembly, which can be considered as a package for Delphi developers. Declaring internal a member of a class, you can access that member from any class inside the same assembly, but not from a class outside the assembly.

The internal syntax can also be used with classes, because if you don't specify a visibility for a class (i.e. public), the class could not be used outside the assembly. To create a class that could be used “only” inside the assembly, declare it as internal.

Destruction

There are important differences between Delphi's memory management and C#'s one. In Delphi you should take care to destroy each and every object you create to avoid memory leaks, but in C# you don't have to worry about that.

Strictly speaking, C# doesn't have destructors, at least not in the way Delphi does. In C# a destructor is like a finalizer and is called by the Garbage Collector when the object is collected. So the destructor is not so useful as in Delphi, we must provide another method for the user of our object to perform cleanup.

Static Fields and Member Functions

In Delphi, the easiest way (and the only way I know) to have a field shared by several instances of the same class, is declare it outside the class. Declare it as a global variable or a local variable under the implementation section. But C# gives us another way to do it, you can declare a member of a class as static, this means, it's value will be shared among all the instances of the same class, and it's accessed through the name of the class, instead the name of the instance.

class StaticClass
{
    public StaticClass()
    {
        instances++;
    }
    public static int instances=0;
}
class StaticTest
{
    public static void Main()
    {
        StaticClass sc=new StaticClass();
        Console.WriteLine(StaticClass.instances);
        StaticClass sc2=new StaticClass();
        Console.WriteLine(StaticClass.instances);
    }
}

Also, you can declare methods of the class as static, and in that case, works in the same way as class methods in Delphi.

Constants

To declare a constant, you just need to declare it with the const modifier and assign it a value, but you must include the type of the value.

public const int value=33;

But there is another powerful feature to allow us initialize constants to a value we don't know at compile time. This feature is called readonly. If we use the readonly modifier instead the const modifier when declaring a constant, we are allowed to set its value only at the constructor of a class. If we try to modify it in another location, we will get an error.

class ReadOnlyClass
{
    public ReadOnlyClass(int value)
    {
        this.value=value;
    }
    public readonly int value;
}

In this way, value could not be modified in any part of the program except in its constructor. Works very similar to readonly properties, but in my opinion, the syntax is much more clear.

Variable Parameters

In Delphi, a function can take a variable number of parameters, C# is able to do this in a fancier way because in C# everything is an object, take a look at this code:

class VariableParameters
{
    public void Write(string line, params object[] args)
    {
        for (int index=0; index < args.GetLength(0); index++)
        {
            Console.WriteLine(args[index].ToString());
        }
    }
}
class TestVariableParameters
{
    public static void Main()
    {
        VariableParameters vp=new VariableParameters();
        vp.Write(“Hello ”, “World!”);
        vp.Write(“Params:”, “a”, “b”, 12, 15);
        object[] arr=new object[4];
        arr[0] = “These”;
        arr[1] = “are”;
        arr[2] = 4;
        arr[3] = “params”;
        vp.Write(“Params:”,arr);
    }
}

If we use the params keyword on a parameter of a function, we are able to pass as many parameters as we want to that function. The compiler internally converts that list of parameters to an array and calls the function with that array, the same as in the third call.

Structs

Structs are really different to Dephi records, because they inherit from object, act like objects but with a few restrictions, they can't inherit from any other type and other classes can't inherit from them. It's really strange to see a struct that has methods, but we must think that is an object, in the same way an integer or a string.

So we can write this code:

struct Rect
{
    public Rect(int x1, int y1, int x2, int y2)
    {
        this.x1 =x1;
        this.y1 =y1;
        this.x2 =x2;
        this.y2 =y2;
    }
    public override string ToString()
    {
        return(String.Format(“({0},{1})-({2},{3})”,x1,y1,x2,y2);
    }
    public int x1;
    public int y1;
    public int x2;
    public int y2;
}
class TestRect
{
    public static void Main()
    {
        Rect bounds = new Rect(100,100,200,200);
        Console.WriteLine(“Bounds: {0}”, bounds);
    }
}

This code is so strange to a Delphi developer, because first, declares methods in a struct, second, a method uses the override reserved word, and third, the struct is written to the console.

We can declare methods in a struct because is an object, because is an object we can override certain functions of the object ancestor (ToString is available for all objects), and the ToString method is called for each an every object is passed to the WriteLine function.

Sentences

I'm not going to write here all the sentences of Delphi and its equivalent in C# because most part are the same in C++, you should read another tutorials/books about that.

Switch

Switch is equivalent to Case in Delphi, but has a total different syntax:

switch (condition)
{
    case 1: 
    case 2:
                    //Do something here for case 1 and case 2
            break;
    case 3:
                    //Do something here for case 3
            break;
    default:
            //Handle here the default case
}

Works very similar to C++ but it requires a break at the end of each case block, this is a common mistake in C++, so the compiler gives an error if there's no break.

Foreach

C# incorporates a feature to make iterations simpler, is called foreach, and allows to iterate easily through an array (also allows another types, but it's outside of the scope of this manual).

Look this code:

public static void ForeachTest(ArrayList myArray)
{
    for (int i=0; i < myArray.Count ; i++)
    {
        MyObject current=(MyObject) myArray[i];
        Console.WriteLine(“Item: {0}”, current);
    }
}

With foreach, you can write the previous code as follows:

public static void ForeachTest(ArrayList myArray)
{
    foreach(MyObject current in myArray)
    {
        Console.WriteLine(“Item: {0}”, current);
    }
}

Return

Return, returns to the calling function and optionally returns a value, is like assign a value to result and make an exit.

As and Is

These operators can be used in the same way in Delphi, you can know the type of an object/interface and force it to be of a specified type.

Strings

Because strings are objects too, you can apply to string variables several methods to compare them, make operations, iterate, etc.

This is a brief list of all the methods that supports a string:

Compare()

CompareOrdinal()

CompareTo()

EndsWith()

StartsWith()

IndexOf()

LastIndexOf()

Concat()

CopyTo()

Insert()

Join()

PadLeft()

PadRight()

Remove()

Replace()

Split()

Substrng()

ToLower()

ToUpper()

Trim()

TrimEnd()

TrimStart()

To make string processing easier, there's a class called StringBuilder which can be useful to make such operations.

Regular Expressions

To deal with regular expressions, there's a class called Regex which handles all this stuff. You just create a new object of Regex class and as parameter you use the regular expresion, then you can use the several methods provided with this class to perform operations to strings.

Properties

The syntax for properties is totally different to Delphi's one. In fact, if you don't pay attention, you can't see is a property, because it looks like a method.

The concept is the same, a property is a way to get the value from a field of an object, or to set that value, a property itself it's not a value. Look that code:

class Test
{
   private string name;
   public string Name
   {
       get
       {
           return name;
       }
       set
       {
           name = value;
       }
   }
}

Personally, I prefer the way Delphi declares properties, for example, you see the value parameter of the set accessor, but here is hidden. If a property has only a get accessor (getter), it's a read-only property, and if it has only a set accessor (setter) it's a write-only property.

Delegates

In Delphi you can write:

type
   TWakeUpEvent=procedure(Sender:Tobject; When:integer) of object;

You are creating a procedural type, then you can declare variables of that type, and this mechanism is needed when declaring events.

In C#, the concept is called delegate, in C#, the previous line can be written as follows:

public delegate void TWakeUpEvent(object Sender, int When);

Because in C#, all the code must be written inside a class, you don't need to specify of object. After that declaration you can write:

class WakeMe
{
   public delegate void TWakeUpEvent(object Sender, int When);
   public void WakeMeUp(TWakeUpEvent wakemeup)
    {
       wakemeup(anobject, 500);
   }
}

But this has some drawbacks, because you must create an instance of the delegate, passing as an argument, the function you want to be called.

class Test
{
   public static void Notified(object Sender, int When)
   {
       Console.WriteLine(“When: {0} “, When);
   }
   public static void Main()
   {
       WakeMe wm=new WakeMe();
       WakeMe.TWakeUpEvent ev=new WakeMe.TWakeUpEvent(Notified);
       WakeMe.WakeMeUp(ev);
   }
}

Events

Events in C# are based in delegates, and there's a design convention to design the delegates to take always two parameters, the first is the object that fired the event and the second parameter is an object that contains the information about the event, this object is always derived from EventArgs. In Delphi, the convention is to define an event with Sender as first parameter and the rest of information in separate parameters.

class MyEventArgs: EventArgs
{
    public MyEventArgs(string name, string value)
    {
        this.name=name;
        this.value=value;
    }
    public string Name
    {
        get
        {
            return(name);
        }
    }
    public string Value
    {
        get
        {
            return(value);
        }
    }
    string name;
    string value;
}
class MyNotify
{
    public delegate void MyEventHandler(object sender, MyEventArgs e);
    public event MyEventHandler OnMyEventHandler;
    protected void OnMyEvent(MyEventArgs e)
    {
        if (OnMyEventHandler != null) OnMyEventHandler(this, e);
    }
    public void NotifyEvent(string name, string value)
    {
        MyEventArgs e = new MyEventArgs(name, value);
        OnMyEvent(e);     
    }
}
class MyWatcher
{
    public MyWatcher(MyNotify myNotify)
    {
        this.myNotify = myNotify;
        myNotify.OnMyEvent += new MyNotify.MyEventHandler(MyFunction);
    }
    void MyFunction(object sender, MyEventArgs e)
    {
        Console.WriteLine(“New Mail:{0}
{1}”, e.Name, e.Value);
    }
    MyNotify myNotify;
}
class Test
{
    public static void Main()
    {
        MyNotify myNotify = new MyNotify();
        MyWatcher myWatcher = new MyWatcher(myNotify);
        myNotify.NotifyEvent(“this is a name”, “This is a value”);
    }
}

The first time I saw this code, I started to think that C# is not as good with events as Delphi, but let's look at the line:

   myNotify.OnMyEvent += new MyNotify.MyEventHandler(MyFunction);

This line assigns a handler to the event, but then, Why +=, instead a simple assignment? In C#, instead assign a pointer to an event, you are “subscribing” to a notification list, that means, several functions can be notified when an event is fired without do anything special. This feature is really useful. In C#, this feature is called multicast, and there are two things you must be aware, the order in which delegates are called is not defined, and if one delegate throws an exception, maybe, the other delegates are not called.

Conclusion

If you were a C++ developer who changed to Delphi due RAD capabilities (this is my story ;-) ), you will feel very comfortable with C#, it gives you the best of C++ with a Delphi flavour.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值