Artikel als PDF anzeigen

Werbung
ITMAGAZINE
C#-Workshop, Teil 2: C#-Objekte im Praxiseinsatz
15. Juni 2001 - Der zweite Teil des C#-Workshops beschäftigt sich mit der Erstellung und Behandlung von
Objekten und zeigt, wie die Leistungsfähigkeit des .Net-Frameworks genutzt werden kann. Ein Objekt definiert sich durch Eigenschaften, Methoden und Ereignisse. Ein entscheidender Aspekt der
objektorientierten Programmierung ist neben der Vererbung vor allem die Kapselung von Programmlogik. Die
Einhaltung dieses Grundsatzes gewährleistet die Erstellung von Objekten, welche dann in einem hohen Masse
wiederwendbar sind und sich universell einsetzen lassen.
In C# wird die Definition eines Objektes eingeleitet durch das Schlüsselwort class. Eine hierdurch erzeugte
Klasse entspricht einem Objekt. Durch Schlüsselwörter, denen class vorangestellt wurde, werden die Sichtbarkeit
dieser Klasse innerhalb des aktuellen Projektes und eine Veröffentlichung gesteuert. Eine als public definierte
Klasse ist von anderen Programmen instanzierbar und kann entsprechend benutzt werden. Hingegen ist eine als
privat gekennzeichnete Klasse weder innerhalb des aktuellen Projektes noch von anderen Programmen
verwendbar. Soll eine Klasse innerhalb des aktuellen Projektes verwendet werden, so ist das Schlüsselwort
internal voranzustellen. Über weitere Schlüsselwörter kann das Verhalten einer Klasse bei der Vererbung gesteuert werden. Soll eine
Klasse lediglich eine Struktur definieren, die von vererbten Objekten genutzt wird, so besteht die Möglichkeit,
eine Klasse als abstract zu kennzeichnen. Eine so definierte Klasse muss in jedem Fall vererbt werden, sie kann
nicht direkt in einem Programm instanziert werden. Der Einsatz eines solchen Konstruktes erlaubt beispielsweise
die Definition eines Standardobjektleistungsumfanges, der dann individuell gefüllt werden kann.
Es gibt auch Fälle, in denen vermieden werden soll, dass eine Klasse abgeleitet wird. Durch Einsatz des
Schlüsselwortes sealed wird die Ableitung verboten und die Klasse damit als endgültig festgelegt. Eigenschaften festlegen
Nach der Definition des Objektes, der Sichtbarkeit und des Vererbungsverhaltens gilt nun die Betrachtung der
Definition von Eigenschaften innerhalb einer Klasse. Es ist zwar generell möglich, eine Variable innerhalb der
Klasse als public zu definieren. Dies entspricht aber nicht dem Gedanken der Kapselung. Durch die Kapselung
von Eigenschaften lässt sich auch das Verhalten beim Zugriff auf diese Eigenschaften im Detail steuern. Zudem
könnte es notwendig sein, bei der Zuweisung von Eigenschaften entsprechende Plausibilitätsprüfungen
durchzuführen. Auch die Abfrage von Eigenschaften könnte dynamisch aufgrund von anderen Eigenschaften
unterschiedliche Werte zurückgeben.
Ein solches Konstrukt zur Definition einer Eigenschaft kapselt prinzipiell den Zugriff auf eine private Variable.
Das folgende Beispiel zeigt die Realisierung einer Eigenschaft Nachname in einem öffentlichen Objekt Person.
Dabei wird der Name bei der Zuweisung immer auf 30 Zeichen gekürzt.
namespace Adressen
{
using System;
public class Person
{
private string_
mstr_Nachname="";
public string Nachname
{
set
{
mstr_Nachname =_
value.Substring(1,30);
}
get
{
return mstr_Nachname;
}
}
}
}
//Codezeilen, die aus Layout//technischen Gründen umbrochen
//werden mussten, werden an der
//betreffenden Stelle mit einem
//Unterstrich gekennzeichnet.
Auf die Deklaration der Klasse erfolgt die Definition einer innerhalb dieser Klasse privaten Variable
mstr_Nachname. Die Zeile public string Nachname leitet die Definition einer Eigenschaft ein. Der set-Block
kommt zur Ausführung, wenn dieser Eigenschaft ein Wert zugewiesen wird. Dabei steht der Wert über den
speziellen Parameter value zur Verfügung. Der get-Block wird bei der Abfrage dieser Eigenschaft durchlaufen.
Hier erfolgt die Rückgabe des Wertes der Eigenschaft über das Schlüsselwort return.
namespace Adressen
{
using System;
public class Test
{
public void Main()
{
//Variable vom Typ
//definieren
Person objPerson;
//objPerson instanzieren
objPerson = new Person();
//Eigenschaft Nachname
//mit Wert versorgen
objPerson.Nachname =_
"Groth";
//Eigenschaft Nachname
//auslesen
Console.WriteLine_
("Nachname:,objPerson._
Nachname);
}
}
}
Oftmals kommt es vor, dass eine Eigenschaft schreibgeschützt sein muss, beispielsweise eine
Status-Eigenschaft. Um dieses zu erreichen, genügt es, den entsprechenden set-Block wegzulassen. Danach
erzeugt eine Zuweisung dieser Eigenschaft einen Laufzeitfehler.
private string mstr_ID="";
public string ID
{
get
{
return mstr_ID;
}
}
Methoden definieren
Die Definition von Methoden entspricht dem unter C# üblichen Standard zur Definition von Prozeduren und
Funktion. Eine Prozedur wird mit dem vorgestellten Schlüsselwort void versehen. Typisch für ein Objekt Person
wäre die Implementierung einer Methode Update, welche die Daten in einer Datenbank speichert.
public void Update()
{
//Code für das Update
//der Datenbank
}
Bei einer Funktion wird dem Namen der Funktion der Rückgabetyp vorangestellt. Als Beispiel bietet sich die
Implementierung der Methode ToString() an. Viele der Basisklassen des .Net-Frameworks stellen diese Methode
zur Verfügung, um den Inhalt des Objektes als Zeichenfolge formatiert ausgeben zu können. Hierbei wurde die
exemplarisch verwandte Klasse Person um die Eigenschaften Anrede und Vorname erweitert.
override public string ToString()
{
return this.Anrede_
+ " " + this.Vorname_
+ " " + this.Nachname;
}
Oftmals stellt es ein Problem dar, dass Funktionen nur genau einen Wert zurückliefern können. C# stellt dazu die
Schlüsselwörter ref und out zur Verfügung, die einem Parameter vorgestellt werden. Während ref mit einer
Referenz auf die dort übergebene Variable arbeitet, bietet out zusätzlich den Vorteil, mit nicht initialisierten
Variablen arbeiten zu können. Prinzipiell entspricht out aber der Arbeit mit einer Referenz.
Um dem Prinzip der Kapselung auch bei Eigenschaften und Methoden gerecht zu werden, ist hier der Einsatz von
Schlüsselwörtern zur Steuerung der Sichtbarkeit notwendig Entsprechend der Definition einer Klasse
veröffentlicht public die entsprechende Eigenschaft oder Methode, private hingegen macht sie nur für die
aktuelle Klasse zugänglich. Soll der Zugriff nur Innerhalb des aktuellen Projektes erfolgen, so ist internal zu
wählen. Zusätzlich kann eine Eigenschaft oder Methode aber auch in ihrem Sichtbarkeitsbereich als protected
definiert werden. Dies hat zur Folge, dass die Sichtbarkeit nur innerhalb von abgeleiteten Klassen gegeben ist. So
können den "Kunden" des Objektes eigentlich private Funktionalitäten exklusiv zur Verfügung gestellt werden.
können den "Kunden" des Objektes eigentlich private Funktionalitäten exklusiv zur Verfügung gestellt werden.
Zusätzlich kann noch die Schlüsselwortkombination internal protected verwandt werden, diese beschränkt die
Möglichkeiten von protected auf das aktuelle Projekt.
Besonderheiten mit abstract
Neben der Festlegung, in welcher Form Eigenschaften und Methoden sichtbar sein sollen, ist natürlich auch die
Steuerung des Objektes bei der Vererbung wichtig. Dabei ist eine Besonderheit bei der Verwendung des Schlüsselwortes abstract zu bedenken. Da hier eine
Implementation in der abgeleiteten Klasse notwendig ist, muss eine Vererbung in jedem Fall stattfinden. Es ist
denn auch nicht möglich, eine Klasse direkt zu instanzieren, die in irgendeiner Form eine abstract-Definition
enthält. Faktisch erhält somit die gesamte Klasse den festgelegten Status abstract.
public abstract class DataEntry
{
public abstract void AddNew();
public abstract void Edit();
public abstract void Delete();
public abstract void Read();
}
Dieses Beispiel definiert die Struktur für einen Typ von Klasse, über welche die Erstellung von Objekten des Typs
Datenbankeinträge möglich wird. In den abgeleiteten Klassen müssen die einzelnen Methoden dann
entsprechend mit Code zum Datenbankzugriff implementiert werden. Statische Methoden und Eigenschaften
Ein weiteres Konzept der objektorientierten Programmierung unter C# sind statische Methoden und
Eigenschaften, die vorwiegend zum Einsatz kommen. Auch wenn es sogar möglich ist, Variablen statisch zu definieren, so ist eine Kapselung über die Erstellung einer
Eigenschaft dennoch vorzuziehen. Durch die Verwendung des Schlüsselwortes static wird eine solche Eigenschaft
unabhängig von allen Instanzen. Ausschlaggebend ist immer die Basisklasse. Das folgende Beispiel realisiert einen Zähler, der angibt, wie oft eine
Instanz der Klasse Person erstellt wurde. Der Code zum Inkrementieren dieses Zählers befindet sich im
Konstruktor der Klasse, welcher im folgenden noch detailliert vorgestellt wird.
private int static_
mint_InstCount=0;
public static int InstCount
{
set
{
mint_InstCount = value;
}
get
{
return mint_InstCount;
}
}
Virtuelle Funktionen dienen zur Optimierung von Methoden unter dem Schlüsselwort virtuell. Virtuell bedeutet
hierbei, dass der Compiler bei abgeleiteten Klassen den tatsächlichen Objekttyp ermittelt und darauf basierend
die am besten geeignete Funktion aufruft. Eine so definierte Methode kann überschrieben werden, ohne Gefahr
zu laufen, dass die in der Basisklasse definierte Funktion aufgerufen wird. Im folgenden wird die Methode
Update der Klasse Person nun als virtuell definiert, um sie später in einem auf Person abgeleiteten Objekt
überschreiben zu können.
virtual public void Update()
{
//Code für Update auf
//die Datenbank einfügen
}
Erstellungsroutinen einsetzen
Als eine komfortable und sichere Erstellung von Objekten zur Laufzeit ist der Einsatz von Erstellungsroutinen
oder Konstruktoren gedacht. Eine solche Erstellungsroutine wird mit dem selben Namen wie die Klasse versehen
und kann über eine beliebige Anzahl von Parametern verfügen. Für die Klasse Person existiert eine
Erstellungsroutine, über die alle wichtigen Eigenschaften sofort bei der Erstellung einer Instanz des Objektes
gesetzt werden können.
public class Person
{
public Person(string_
Anrede,string Vorname,_
string Nachname)
{
this.Anrede = Anrede;
this.Vorname = Vorname;
this.Nachname = Nachname;
//Instanzenzähler erhöhen
mint_InstCount++;
}
...
Dieser Quelltext macht von dem Schlüsselwort this Gebrauch, das einen Verweis auf die aktuelle Klasse enthält.
So können die Eigenschaften der eigenen Klasse angesprochen werden. An dieser Stelle macht der Einsatz eines
weiteren Konzeptes der objektorientierten Programmierung Sinn: Durch Überladung kann eine weitere
Erstellungsroutine unter dem selben Namen im Quelltext vorhanden sein, es müssen sich lediglich die Parameter
unterscheiden. Wird eine so überladene Funktion aufgerufen, wählt der Compiler die geeignete Funktion durch
unterscheiden. Wird eine so überladene Funktion aufgerufen, wählt der Compiler die geeignete Funktion durch
Abgleich der Parameter im Aufruf mit den für die Methode definierten Parametern. Die zuerst erstellte Routine
lässt lediglich das Füllen des Objektes mit neuen Daten zu. Soll eine Person aber beispielsweise direkt aus der
Datenbank über eine ID abgerufen werden, so könnte eine überladene Erstellungsroutine den folgenden Aufbau
haben.
public class Person
{
public Person(string_
Anrede,string Vorname,_
string Nachname)
{
this.Anrede = Anrede;
this.Vorname = Vorname;
this.Nachname = Nachname;
//Instanzenzähler erhöhen
mint_InstCount++;
}
public Person(string ID)
{
mstr_ID = ID;
//Instanzenzähler erhöhen
mint_InstCount++;
//Datenbankzugriff über ID
//hier programmieren
}
}
Bei der Instanzierung des Objektes Person kann nun wahlweise entschieden werden, ob eine neue Person
angelegt werden soll oder ob auf eine vorhandene Person über die ID zugegriffen wird. Dies ermöglicht eine sehr
effiziente Instanzierung von Objekten. Der folgende Ausschnitt aus einem Quelltext zeigt beide Varianten.
public class Test
{
public void Main()
{
//Variable vom Typ
//Person definieren
Person objPerson;
//2.Variable vom Typ
//Person definieren
Person objPerson2;
//Instanz objPerson
//erstellen
objPerson = new_
Person("Herr",_
"Frank","Groth");
//Instanz objPerson2
//durch überladene
//Erstellungsroutine
objPerson2 = new_
Person("ID1");
}
}
Die Erzeugung von Klassen
Nachdem die Basiskonzepte der Vererbung unter C# erläutert wurden, soll nun auf Basis der Klasse Person eine
spezielle Klasse erzeugt werden. Hierzu wählen wir einen Angestellten. Dieser verfügt im Beispiel als weitere
Eigenschaft über ein Gehalt. public class_
Angestellter:Person
{
private double mdbl_Gehalt = 0;
public Angestellter(string_
Anrede,string Vorname,string_
Nachname,double Gehalt)_
:base(Anrede,Vorname,Nachname)
{
{
this.Gehalt = Gehalt;
}
public double Gehalt
{
set
{
mdbl_Gehalt = value;
}
get
{
return mdbl_Gehalt;
}
}
override public void Update()
{
//spezieller Code, um einen
//Angestellten in der
//Datenbank zu erfassen
}
override public string_
ToString()
{
return base.ToString()+_
" Gehalt: " +_
this.Gehalt.ToString();
}
}
}
In der Definition der Klasse mit dem Namen Angestellter wird durch den Doppelpunkt und den darauf folgenden
Namen der Klasse Person angegeben, dass die Klasse Angestellter eine Vererbung der Klasse Person ist.
Damit stehen alle entsprechend definierten Eigenschaften und Methoden der Klasse Person auch als Angestellter
zur Verfügung. Da diese Klasse über eine zusätzliche Eigenschaft Gehalt verfügt, muss die Erstellungsroutine
angepasst werden. Dabei wird die Erstellungsroutine der Klasse Person genutzt. Das Schlüsselwort base,
welches ebenfalls durch einen Doppelpunkt getrennt auf die Definition folgt, ruft die entsprechende
Erstellungsroutine der Klasse Person auf. Das Gehalt ist nur bei einem Angestellten definiert, also ist eine
entsprechende Eigenschaft zu implementieren. Das Gehalt sollte aus der Erstellungsroutine direkt mit
angesprochen werden können.
Da die Datenbankstruktur eines Angestellten sich von einer Person unterscheidet, muss die Methode Update
ebenfalls angepasst werden. Diese wurde in der Personenklasse als virtuell definiert und kann nun mit override
überschrieben und angepasst werden. Dasselbe gilt für die Methode ToString. Diese verwendet über
base.ToString() die in der Klasse Person bereits implementierte Methode ToString und hängt lediglich das Gehalt
an.
C# und das .Net-Framework
Im ersten Teil dieses Workshops wurde bereits auf die Ausrichtung von C# auf die Erstellung von Komponenten
hingewiesen. Im folgenden soll nun eine solche Komponente erstellt werden. Diese Komponente basiert auf der
bereits vorgestellten Klasse Person. Zuerst wird eine zusätzliche Eigenschaft Email implementiert und die
Methode ToString um die Ausgabe der in Klammern gesetzten Eigenschaft Email erweitert.
Als überladene Erstellungsroutine wurde bereits eine Methode mit dem Parameter vorgesehen. Diese soll auf
eine Datenbanktabelle zugreifen, dort den entsprechenden Datensatz anhand der ID finden und die
Eigenschaften der Klasse mit den gelesenen Werten füllen. Unter .Net enthält das Framework eine spezielle
Klassenbibliothek mit dem Namen ADO.Net. ADO.Net ist die auf .Net abgestimmte Weiterentwicklung der
allgemein bekannten Active Data Objects, die heutzutage den Standard beim Datenbankzugriff unter Windows
darstellen. Da das gesamte .Net-Framework sich über Klassenbibliotheken präsentiert, ist die Nutzung
entsprechender Funktionalitäten sehr einfach. So gibt es unter ADO.Net einige spezielle Objekte, welche sich
gezielt mit der Anbindung an Microsoft SQL Server befassen. Diese werden im folgenden bei der Kapselung des
Datenbankzugriffes benutzt. Vor der Nutzung von ADO.Net muss der entsprechende Namespace dem aktuellen
Projekt bekannt sein. Dazu sollten folgende using-Zeilen vorhanden sein. Im folgenden wird der erweiterte
Quelltext für die Routine Person gezeigt.
Es werden drei Objekte aus ADO benötigt, um die in SQL Server vorhandene Tabelle "Personen" in der Datenbank
"Adressen" anzusprechen zu können. SQLConnection zur Verbindung mit der Datenbank, SQLCommand zur
Aufnahme der Select-Anweisung und ein SQLDataReader zum Lesen der Daten. Dieser verhält sich nur vorwärts
lesend. Über den Aufruf der Methode Read wird der Datenbank-Cursor weiterbewegt. Da hier nur mit maximal
einem Datensatz gerechnet werden kann, reicht eine einfache if-Bedingung. Eine Datenbankanbindung ist
generell sehr fehlerträchtig, also muss eine entsprechende Ausnahmebehandlung implementiert werden. Die
Methode macht keine eigene Fehlerbehandlung, sondern löst aufgetretene Fehler über throw neu aus. Die
Fehlerbehandlung sollte dann in dem aufrufenden Programm stattfinden. Dieses Vorgehen macht diese
Komponente unempfindlich gegen Probleme. Abschliessend wird die gesamte Klasse dann als adressen.dll in Form einer Assemblierung kompiliert und kann
über den Namespace Adressen weiterverwendet werden.
Um die erstellte Komponente über using Adressen einzusetzen, wird eine klassische Windows-Anwendung
erstellt. Das .Net-Framework liefert über WinForms einen Satz von Bibliotheken, die darauf abgestimmt sind. Das
im untenstehenden Bild abgebildete Formular hat die Aufgabe, nach Eingabe einer ID den zugehörigen
Personentext anzuzeigen. Ein direkter Datenbankaufruf aus dem Formular heraus ist nicht gewollt, dafür wurde
die Komponente Adressen entwickelt.
Die durchgängig objektorientierte Struktur des .Net-Frameworks wird bereits bei der Definition der
Formularklasse deutlich. Diese leitet sich nämlich direkt von System.WinForms.Form ab. Danach folgt für alle
Steuerelemente eine Variablendefinition und in der Erstellungsroutine PersonAnzeigen wird die Initialisierung
aller Controls und des Formulars aufgerufen. Aus Gründen der Übersichtlichkeit wurde der hier notwendige Code ausgeblendet. Er kann aber jederzeit über
den WinForms-Designer neu erstellt werden. Für die Funktionalität wichtig ist die Zuweisung einer
Ereignisprozedur beim Klicken auf den Button Lesen. In dieser Prozedur wird ein Personenobjekt instanziert,
welches über den Namespace Adressen zur Verfügung steht. Bei der Erstellung wird die eingegebene ID
übergeben. Dies führt dann zu einem Datenbankzugriff in der vorher erstellten Komponente. Durch den
folgenden Aufruf der Methode ToString des Objektes Person wird dann der Inhalt des Objektes objPerson
angezeigt. Da das Objekt Person alle Fehlermeldungen weitergibt, ist hier eine entsprechende Fehlerbehandlung
notwendig. Abschliessend erfolgt noch die Definition einer Methode Main. Diese dient als Startpunkt der
WinForms-Anwendung, indem sie eine Instanz des Formulars erstellt.
Die Weiterentwicklung von ASP
Unter .Net hat die ASP-Technologie mit ASP.Net sicher den grössten Fortschritt gemacht. So ist es jetzt unter
anderem möglich, serverseitig auf Ereignisse zu reagieren. Zudem erfolgt die Erstellung solcher Anwendungen
nun objektorientiert und nimmt eine klare Trennung zwischen dem Programmcode und dem Layout vor. Dabei ist
ASP.Net auf die Nutzung von HTML 3.2 ausgerichtet. Dieses gewährleistet eine grösstmögliche
Browser-Unabhängigkeit. Die Möglichkeit, auf Ereignisse reagieren zu können, erlaubt das bereits als
Winforms-Anwendung realisierte Programm zur Nutzung der Komponente Adressen auch als Web-Anwendung
zu erstellen. Dabei ist der Programmcode, der durch einen Klick auf den Button Lesen ausgelöst wird, identisch
mit der WinForms-Anwendung.
mit der WinForms-Anwendung.
Der zugehörige HTML-Code ist hier nicht abgebildet. Das Ereignis Page_Load, welches beim Aufruf der Seite
ausgelöst wird, dient in diesem Fall zur Ausgabe einer Kopfzeile. Mit ASP.Net ist die Erstellung von interaktiven,
komponentenbasierenden Anwendungen einfach geworden. ASP-Anwendungen werden kompiliert und erzielen
somit eine hohe Performance. Mit C# ist nun erstmals ein Sprache der C-Familie für die Entwicklung solcher
Anwendungen verfügbar. Fazit
In diesem Workshop konnten weder alle Möglichkeiten von C# aufgezeigt, noch die Mächtigkeit des
.Net-Frameworks in Verbindung mit C# erschöpfend behandelt werden. Es sollte vielmehr Lust geweckt werden,
sich einmal C# anzusehen. Wer bereits C++ kennt, wird wenig Probleme haben, auch Java ist nicht die ganz
grosse Hürde. Wer sich mit Visual Basic beschäftigt, wird sich unter .Net sowieso mit einem neuen
Sprachumfang auseinandersetzen müssen. Vieles in Visual Basic.Net gleicht C#. Für die Erstellung von
Anwendungen und Komponenten für .Net ist C# auf jeden Fall eine absolute Empfehlung. Copyright by Swiss IT Media 2017 
Herunterladen