Dit artikel is in twee delen en gaat over de verschillen en overeenkomsten tussen CA-Visual Objects en C#. Vandaag deel 1. In het volgende SDGN magazine het tweede en laatste deel. Wat staat de VO programmeur te wachten als hij de stap naar .NET maakt en C# gaat gebruiken? Van welke nieuwe features kunnen we in C# gebruik gaan maken en welke praktijken dienen we te verlaten? Ook voor VO-ers die voorlopig niet van plan zijn de overstap naar .NET te maken, kan dit artikel interessant zijn om ideëen op te doen welke features men in VO 3.0 geïmplementeerd wil hebben. In onderstaand verhaal komt het .NET framework en de base classen slechts zijdelings aan de orde, we beperken ons voornamelijk tot de taal. Nu is het wel zo dat de wijze waarop de classes in .NET geïmplementeerd zijn, vaak niet los gezien kan worden van verschillende taal eigenschappen. Uiteraard geldt dit niet alleen voor C#, maar ook voor de andere .NET talen zoals Visual Basic.NET. Een aantal van dergelijke gevallen behandel ik hier toch.
Inleiding
C# is een taal specifiek ontwikkeld voor .NET. Zij komt in een aantal opzichten sterk overeen met Java. C# is sinds februari 2002 op de markt en heeft zich in korte tijd opgewerkt tot een populaire programmeertaal en niet ten onrechte.
Voor wie VO beheerst is de overgang naar C# op zich niet echt een grote stap. VO-ers hebben met de overstap naar C# beslist het voordeel dat ze al een degelijke, goed gestructureerde, object georiënteerde programmeertaal beheersen. En dat maakt op zich de overgang een heel stuk eenvoudiger dan wanneer je er vanuit bijvoorbeeld Visual Basic in zou stappen. VO en C# hebben ook een paar dingen overeenkomstig. Zo hebben beide talen bijvoorbeeld een garbage collector, waarbij moet worden aangetekend dat de garbage collector van C# meer iets is van .NET zelf dan specifiek C#. Verder hebben beide talen een manier om interne eigenschappen van een object aan de buitenwereld te tonen. In VO gaat dit middels Accessen en Assigns, C# ken hier voor Get’s en Set’s.
C# is, in tegenstelling tot VO, een Strong typed taal
In zijn algemeenheid zou je kunnen zeggen dat C# minder descriptief is. Een lus, of een IF / ENDIF constructie worden gewoon aangegeven met een paar accolades, terwijl daar in VO een grote variatie aan termen voor wordt gebruikt. Sommigen vinden dit sobere, C-achtige karakter van de taal prettig, anderen niet. Het is een kwestie van smaak. Wat voor de meeste VO-ers wel even wennen zal zijn is dat C# een Case Sensitive taal is.
Strong Typing
C# is, in tegenstelling tot VO, een Strong Typed taal. De compiler maakt daar ook echt een punt van als iets niet goed is. Strong Typing gaat vaak ten koste van de flexibiliteit van een taal, probeer bijvoorbeeld maar eens in VO een Strong Typed function met een verkeerd aantal parameters aan te roepen. C# heeft een aantal features om dergelijke beperkingen aan flexibiliteit te ondervangen. Een daarvan is het gebruik van meerdere versies van een methode.
public void Schrijf()
{
this.WriteLine(this.ToString());
}
public void Schrijf(string werkstring)
{
this.WriteLine(werkstring);
}
public void Schrijf(int aantal)
{
this.WriteLine(Convert.ToString(aantal))
}
Deze wijze van werken heet ‘overloading’ en wordt in .NET zeer veel toegepast. Methodes kunnen soms tientallen overloads hebben.
Het bovenstaande is in VO niet Strong te Typen, tenzij je er drie verschillende methodes van maakt. Maar in VO hoef je het ook niet Strong te Typen als je dat niet wilt. Hetzelfde probleem is in VO ook gewoon als volgt op te lossen.
METHOD Schrijf (uVar) CLASS MyClass
IF IsNil(uVar)
SELF:WriteLine(SELF:Name)
ELSE
IF IsString(uVar)
SELF:WriteLine(uVar)
ELSE
IF IsNumeric(uVar)
SELF:WriteLine(Str(uVar))
ENDIF
ENDIF
ENDIF
Declaratie van variabelen
Wat beslist wennen is voor VO-ers, is de manier waarop in C# variabelen gedeclareerd kunnen worden. In principe kan dit namelijk overal, terwijl het in Visual Objects alleen aan het begin van een entiteit kan. Dit heeft zijn voors en zijn tegens. Belangrijk pluspunt vind ik dat je de scope van een variabele veel verfijnder kunt bepalen dan het geval is in Visual Objects. Het volgende is bijvoorbeeld mogelijk:
public void MijnMethode(bool Actie)
{
if (Actie)
{
string Hello = “Hello World”;
this.WriteLine(Hello);
}
}
In bovenstaand voorbeeld is de scope van de string Hello beperkt tot de if clausule. Elders zal de string Hello niet bekend zijn. Gelukkig is Visual Studio.NET heel strikt in dit soort dingen en genereert al een foutmelding als je de variabele Hello probeert aan te roepen buiten zijn scope, nog voor je het gecompileerd hebt.
Wat beslist wennen is voor VO-ers, is de manier waarop in C# variabelen gedeclareerd kunnen worden
Helaas stelt de mogelijkheid overal variabelen te declareren de programmeur ook in staat er flinke spaghetti van te maken. Het equivalent van ‘SELF’ in Visual Objects is ‘this’ in C#. Net als in Visual Objects ben je in C# niet verplicht hiervan gebruik te maken als je een instance variabele benadert. Combineer dit met de mogelijkheid overal je variabelen te declareren, desnoods aan het eind van een module, entiteit of class, of gewoon ergens middenin, en het wordt al snel een heel onoverzichtelijk geheel.
Classes
In C# behoort alles tot een class. Wat dat betreft lijkt het wel een beetje op een taal als Smalltalk. Ook datatypen behoren tot een class. Hetgeen overigens ook weer de mogelijkheid biedt om je eigen data types te creëren. In VO kun je een class instantiëren en ervan overerven. In C# zijn er wat meer nuances en de programmeur heeft nogal wat mogelijkheden om te bepalen of er, en op wat voor manier, overgeërfd kan worden. Abstracte classes kunnen in C# niet geïnstantieerd worden en er moet van worden geërfd om er iets mee te kunnen doen. Het tegenovergestelde zijn de zogenaamde ‘sealed’ Classes waarvan helemaal niet overgeërfd kan worden.
Class attributen en geneste Classes
.NET bezit in het algemeen een veel groter en verfijnder arsenaal aan middelen om de toegang tot classes en methoden van classes te regelen dan VO. Zo kennen de .NET talen C# en VB.NET, in tegenstelling tot VO, attributen die aan een class toegekend kunnen worden. Er zijn vier standaard attributen, namelijk public, protected, private en internal. Daarnaast is het mogelijk zelf attributen te definiëren. De standaard attributen kunnen zowel van toepassing zijn op een class als op een afzonderlijke instance variabele of methode, zoals in VO het geval is. Public is dan enigszins te vergelijken met EXPORT, protected met PROTECT en private met HIDDEN. Internal heeft geen equivalent in Visual Objects. In Visual Basic is er wel iets overeenkomstigs, namelijk ‘friend’. In .NET kun je direct gebruik maken, of overerven, van classes van een andere .NET applicatie. Een prachtige feature en een enorme stap vooruit ten opzichte van het oude OLE. Maar als je niet wilt dat iedere andere .NET applicatie van al jouw classes gebruik maakt, dan declareer je ze als internal. Een internal class is dus alleen zichtbaar binnen de eigen applicatie en verder niet.
Classes kunnen in C# en overigens ook in VB.NET genest worden. Een class declaratie valt dan volledig binnen de omhullende class. Het doel hiervan is om een zekere mate van bescherming te bieden aan de geneste class. Deze kan immers uitsluitend worden benaderd via de omhullende class. Het volgende voorbeeld maakt dit duidelijk:
public class A
{
public class B
{
public B()
{
}
}
public A()
{
}
}
//Het aanroepen van B van elders gaat dan als volgt:
private void Call_B()
{
A.B b = new A.B();
}
B kan hier dus duidelijk niet direct worden aangeroepen, maar alleen via A. Omdat de declaratie van een class dan binnen een andere class plaatsvindt, kan er dan een andere scope dan public aan toegekend worden, bijvoorbeeld protected of private. Bij geneste classes is het in de meeste gevallen zelfs wenselijk dat ze een andere scope dan public krijgen. Zij zijn in eerste instantie bedoeld voor intern gebruik door de omvattende class en niet om door andere classes te worden aangesproken. Bijzondere eigenschap is natuurlijk dat de methodes van een geneste class toegang hebben tot de private en protected methodes van de omvattende class.
Interfaces
Een feature die veel voorkomt in alle .NET talen, maar ook in Java, is het ‘interface’. Het begrip ‘interface’ is natuurlijk algemeen bekend van de COM wereld. Behalve een paar globale overeenkomsten is de implementatie echter geheel verschillend.
Omdat een interface eigenlijk te beschouwen is als een set van afspraken, kunnen interfaces meestal ook niet zomaar gewijzigd worden
Een omschrijving van een interface in .NET zou het volgende kunnen zijn: Een interface is een verklaring dat een class een set aan methodes met specifieke argumenten bevat. Een class die deze methodes heeft, implementeert dan het interface. Een interface is eigenlijk een afspraak of contract, dat de class de methodes bevat zoals beschreven in dat interface. In een interface worden de prototypen van methoden gedeclareerd, maar de inhoud ervan zelf niet. Die inhoud staat juist in de classes die het interface implementeren. Als een class een bepaald interface implementeert, bijvoorbeeld iDispose, dan kan die class benaderd worden als zijnde van het type iDispose, zowel als via zijn eigen type. Overigens ondersteunt VO ook interfaces, maar het wordt niet ondersteund door een eigen syntax, dit moet je zelf regelen met behulp van UDC’s. Je moet er heel wat meer werk voor doen en het is ook niet erg bekend. Dit laatste komt waarschijnlijk mede doordat de VO GUI classes interfaces niet ondersteunen. Mogelijk hierdoor is het begrip interface onder VO programmeurs niet erg bekend. Aan het eind dit verhaal kom ik nog terug op hoe interfaces te implementeren zijn in VO. In .NET zijn interfaces in ieder geval heel belangrijk en je komt ze ook overal tegen. Twee interfaces wil ik hier toch even nader beschouwen. De eerste is iFormatProvider. Het iFormatprovider interface kom je iedere keer weer tegen als je numerieke of datum waarden op het scherm wilt tonen. Als je wilt kun je dan een van de overloads gebruiken die een class van het type iFormatProvider vereisen. Dit kan bijvoorbeeld de CultureInfo class zijn. Een en ander is weergegeven in Figuur 1.

Fig. 1
In het bovenstaande geval zal de datum op een standaard Nederlandse wijze worden vertoond, gebruik makend van de implementatie van het iFormatProvider interface in de CultureInfo class.
Een ander belangrijk interface is het iDispose interface. Alle classes in .NET waar na gebruik resources moeten worden vrijgemaakt ondersteunen dit interface. Dit zijn natuurlijk Forms (in VO Windows), Icons, Bitmaps, classes die low level naar een bestand schrijven etc. etc. Eigenlijk gaat het hier om hetzelfde soort classes als die we ook in VO vinden, waar iets soortgelijks mee aan de hand is. In C# heeft de implementatie van het iDispose interface tot een extra betekenis van een keyword geleid. Zo wordt het ‘using’ statement onder meer gebruikt om weer van objecten af te komen die het iDisposable interface hebben gedefinieerd. Dit using statement in C# moet overigens vooral niet worden verward met het using directive, waarmee aangeduid wordt van welke onderdelen een class gebruik dient te maken en dat in ieder stuk C# code terug te vinden is. De syntax van het using statement is als volgt:
using ( " )
{
//programma code
}
In de hieronder staande routine komt het gebruik van het using statement aan de orde:
private void CreerGraphics()
{
using (Pen een_pen = new Pen(Color.CornflowerBlue,10))
{
using (Graphics g = this.CreateGraphics())
{
g.DrawEllipse(een_pen,200,100,20,40);
}
}
}
In de code hierboven worden een object van de class Pen en een object van de class Graphics geïnstantieerd. Als de uitvoering van de code van het using statement bij de tweede accolade is aangeland, wordt de Dispose() methode van het betreffende object aangeroepen en het object vernietigd. In bovenstaand voorbeeld zien we een genest gebruik van het using statement en betreft het een object van de class Pen en een object van de class Graphics. Zonder gebruik te maken van dit using statement en uiteraard het interface, zou dezelfde code ook als volgt kunnen worden geschreven:
private void CreerGraphics()
{
Pen een_pen = new Pen(Color.CornflowerBlue,10);
Graphics g = this.CreateGraphics();
g.DrawEllipse(een_pen,200,100,20,40);
g.Dispose();
een_pen.Dispose();
}
De wijze waarop in C# interfaces geïmplementeerd worden is via overerving. Een class kan overerven van een andere class en van een of meerdere interfaces. De volgende code geeft een idee van een class die overerft van een andere class en 2 interfaces implementeert:
public Class DerivedClass: BaseClass, Iface1, Iface2
{
public DerivedClass()
{
}
}
Het gebruik van interfaces heeft zijn voors en zijn tegens. Interfaces kunnen onder bepaalde omstandigheden heel handig zijn. Maar ze zijn ook heel star. Omdat een interface eigenlijk te beschouwen is als een set van afspraken, kunnen interfaces meestal ook niet zomaar gewijzigd worden. Dus voor het bouwen van prototypen of applicaties waar later waarschijnlijk toch nog wat in gaat veranderen zijn interfaces niet geschikt.
Zoals hierboven al even genoemd ondersteunt VO interfaces. Eigenlijk beter gezegd, je kunt met het nodige werk zelf interfaces implementeren in Visual Objects. Het voert binnen de scope van dit artikel echter te ver om helemaal uit de doeken te doen hoe dit moet. Waar het in grote lijnen op neer komt is dat je in een UDC file de methodes van het te bouwen interface implementeert. Iets in de geest van:
IMPLEMENTS IFILEIO => declare method write;; ;
Declare method read;; ;
Declare access Filename;; ;
Declare assign Filename;;
Bij de implementatie kunnen we dan zeggen:
CLASS B INHERIT A
PROTECT cName AS STRING
PROTECT oOwner AS OBJECT
IMPLEMENTS IFILEIO
De preprocessor zal vervolgens het IMPLEMENTS IFILEIO statement vertalen. Het is dan aan de gebruiker om te zorgen dat de verschillende methodes correct geïmplementeerd zijn. Wil je het allemaal netjes draaiend krijgen dan komt er nog wel wat meer bij kijken, maar dit is een beetje het globale idee.
Conclusie
Uit de voorbeelden die hierboven genoemd zijn valt op te maken dat veel features van C#, eigenlijk niet zozeer features van C# zelf zijn, als wel het gevolg zijn van de structuur en de wijze waarop inheritance en het gebruik van interfaces is geïmplementeerd in de .NET framework classes. In het volgende deel zal nog wat dieper op de materie ingegaan worden en zullen we onder meer events, delegation, het casten van objecten en fout afhandeling behandelen.