C# anhand von ECMA-334 von Daniel Wiese Matr.Nr. 701930 07.12

Werbung
UNIVERSITY OF APPLIED SCIENCES
Grosses Seminar
WS 04/05
Prof. Dr. Bachmann
Thema:
C# anhand von ECMA-334
von
Daniel Wiese
Matr.Nr. 701930
07.12.2004
Inhaltsverzeichnis
1. Einleitung ............................................................................................................................ 3
2. Die Geschichte von C# .................................................................................................... 3
3. Standard ECMA – 334 .................................................................................................... 3
4. Entwicklungsumgebung .................................................................................................. 4
5. Sprachelemente von C# ................................................................................................... 4
5.1 Das erste Programm ......................................................................................................... 4
5.2 Datentypen ....................................................................................................................... 5
5.2.1 Typumwandlung........................................................................................................ 6
5.2.2 Arrays ........................................................................................................................ 7
5.2.3 Vereinheitlichung der Datentypen ............................................................................ 7
5.3 Variablen und Parameter.................................................................................................. 8
5.4 Automatische Speicherverwaltung................................................................................... 9
5.5 Ausdrücke....................................................................................................................... 10
5.6 Anweisungen (Statements)............................................................................................. 11
5.7 Klassen ........................................................................................................................... 11
5.7.1 Konstanten (constants) ............................................................................................ 12
5.7.2 Felder (fields) .......................................................................................................... 12
5.7.3 Methoden (methods) ............................................................................................... 12
5.7.4 Eigenschaften (properties)....................................................................................... 13
5.7.5 Ereignisse (events) .................................................................................................. 14
5.7.6 Operatoren (operators) ............................................................................................ 14
5.7.7 Indizierer (indexers) ................................................................................................ 16
5.7.8 Instanzkonstruktoren (instance constructors).......................................................... 16
5.7.9 Destruktoren (destructors)....................................................................................... 17
5.7.10 Statische Konstruktoren (static constructors)........................................................ 17
5.7.11 Vererbung (inheritance) ........................................................................................ 18
5.8 Strukturen (structs)......................................................................................................... 18
5.9 Schnittstellen (interfaces)............................................................................................... 19
5.10 Delegaten (delegate)..................................................................................................... 20
5.11 Enumerationen (enums) ............................................................................................... 20
5.12 Namespaces und Assemblies ....................................................................................... 21
5.13 Versioning .................................................................................................................... 22
5.14 Attribute (attributes)..................................................................................................... 22
6. Quellen .............................................................................................................................. 23
1. Einleitung
Das vorliegende Dokument soll eine knappe Übersicht über die Programmiersprache C# , so
wie sie in dem Standard der Europäischen Standardisierungsbehörde ECMA festgelegt wird,
darstellen. Der Autor setzt voraus, dass die Leser dieser Ausarbeitung bereits Erfahrung mit
anderen Programmiersprachen gesammelt haben. Insbesondere wären Kenntnisse in Java- und
C/C++ Programmierung vom Vorteil, da die hier vorgestellte Sprache C# weitgehend auf
C/C++ basiert und auch viele Sprachelemente aus Java übernommen hat. Die Gliederung der
Ausarbeitung entspricht weitgehend der Struktur des ECMA-334 Standards, sodass es
möglich sein sollte die entsprechenden Passagen im Standard zu verfolgen. Es soll gezeigt
werden, dass mit etwas Erfahrung im Programmieren auch das Erlernen anderer Sprachen
anhand von offiziellen Standards möglich ist.
2. Die Geschichte von C#
Mitte der 90-er begann der große Siegeszug der plattformunabhängigen Programmiersprache
Java. Als Microsoft daraufhin versuchte, sich Marktanteile davon zu sichern und mit der
Weiterentwicklung der Umgebung begann, wurde dieses Vorhaben durch ein
Gerichtsbeschluss von der Firma Sun gestoppt. Deshalb wurde von Microsoft beschlossen,
eine eigene Umgebung von Null an zu entwickeln. Die Sprache C# (sprich „see-sharp“ oder
auch „cis“ im deutschen Sprachraum) wurde von der Firma Microsoft entwickelt und wurde
Ende 2000 zusammen mit dem .NET-Framework („dot-net“ ausgesprochen), einer neuen
innovativen Programmier- und Laufzeitumgebung, als direkter Konkurrent zu Sun’s Java in
einer Vorabversion veröffentlicht. C# sollte dabei die Vorzeigesprache der Umgebung
werden. Wie der Name schon sagt basiert C# zum größten Teil auf den bewährten Sprachen C
und C++ aber auch viele Elemente aus Java wurden übernommen. Im Sommer Jahr 2000
wurde C# von Microsoft bei der Europäischen Standardisierungsorganisation ECMA
(European Computer Manufacturers Association) zur Standardisierung eingereicht. Ein Jahr
später veröffentlichte die ECMA den Standard ECMA-334 „C# Language Specification“,
welcher auch 2003 als Grundlage für den ISO Standard (ISO/IEC 23270) diente.
3. Standard ECMA – 334
Der Standard ECMA-334 „C# Language Specification“ ist mittlerweile in der zweiten
Edition frei auf der Seite von der Europäischen Standardisierungsorganisation verfügbar
(http://www.ecma-international.org/publications/standards/ecma-334.htm).
Der Standard spezifiziert die Form und legt die Interpretation der Programme fest, die in der
Programmiersprache C # geschrieben werden.
Er legt fest:
• Die Struktur der C# Programme
• Die Syntax und die Einschränkungen der Sprache C#
• Semantische Regeln zur Interpretation der C# Programme
• Einschränkungen und Grenzen der konformen Implementierung
Er legt nicht fest:
• Mechanismen für die Implementierung der C# Programme für die datenverarbeitende
Systeme
•
•
•
•
•
Mechanismen für die Ausführung der C# Anwendungen
Mechanismen zur Umwandlung der Eingabe-Daten für die Nutzung von den C#
Anwendungen
Mechanismen zur Umwandlung der Ausgabe-Daten nach der Erzeugung von den C#
Anwendungen
Den Umfang oder Komplexität eines Programms und seiner Daten, welches die
Kapazität der verarbeitenden Systeme übersteigen kann
Alle minimalen Anforderungen an ein System, um die konforme Implementierung zu
unterstützen.
4. Entwicklungsumgebung
Für die Entwicklung der Anwendungen in C# bietet Microsoft ihr Produkt „Visual Studio
.NET“ als Entwicklungsumgebung. Diese ist recht umfangreich und sicherlich sehr verbreitet.
Es gibt aber auch sehr gute kostenlose Alternativen, denn das .NET Framework selbst ist frei
verfügbar und darf kostenlos genutzt werden.
Für die Entwicklung eigener Projekte werden mehrere Komponenten benötigt:
• Das .NET – Framework von Microsoft für Windows
(http://www.microsoft.com/downloads/details.aspx?displaylang=de&FamilyID=262d
25e3-f589-4842-8157-034d1e7cf3a3)
• Das .NET – Framework SDK ebenfalls von Microsoft
(http://www.microsoft.com/downloads/details.aspx?familyid=9B3A2CA6-3647-40709F41-A333C6B9181D&displaylang=de)
• Ein Texteditor zum Erstellen von Programmen
Für Unix und Windows gibt es ebenfalls eine freie .Net - Implementierung namens Mono
(http://www.mono-project.com), welcher aber noch ohne Forms (GUI) auskommen muss, und
deshalb erst mal nur GTK+ Komponenten verwendet.
Zum Schreiben von Quelltexten reicht natürlich auch ein einfacher Texteditor,
Entwicklungsumgebungen bieten jedoch eine komfortablere Arbeit, schon allein deswegen,
weil der Compiler nicht jedes Mal per Hand auf der Konsole aufgerufen werden muss.
Darüber hinaus bieten sie „Code Competition“ und bessere Projektverwaltung. Eine sehr
solide Umgebung ist Borland C# Builder
(http://www.borland.com/products/downloads/download_csharpbuilder.html) und ist für den
Privatgebrauch kostenlos. Eine weitere Alternative ist das Open-Source Projekt
SharpDevelop (http://www.sharpdevelop.net/OpenSource/SD), das ständig weiterentwickelt
wird.
5. Sprachelemente von C#
5.1 Das erste Programm
Als erstes Beispiel eines einfachen C# Programms wird das schon mittlerweile fast zum
Standard gewordene „Hallo Welt!“.
using System;
class Hello {
static void Main() {
Console.WriteLine("Hallo Welt!");
}
}
Wie man schnell sieht, ist C# wie Java rein objektorientiert, auch die Struktur der
Anweisungen entspricht der aus Java bekannten Notation. Dementsprechend gibt es keine
global definierten Methoden oder Variablen.
Der Quellcode wird typischerweise in einer Textdatei mit Endung .cs abgespeichert und wird
z.B. beim .NET C# Compiler mit dem Befehl csc hello.cs kompiliert, die erstellte Anwendung
erzeugt die Ausgabe „Hallo Welt!“ auf der Konsole. Der Dateiname muss übrigens nicht wie
in Java mit dem Klassennamen identisch sein. Der Startpunkt jeder C# Anwendung ist immer
die statische (static) Methode Main. Alle benötigten Standard - Klassenbibliotheken sind in
CLI (Common Language Infrastructure) enthalten. Der Compiler erzeugt ein Zwischencode in
MSIL (Microsoft Intermediate Language), der danach vom Just-In-Time Compiler teilweise
in Maschinencode übersetzt wird. Um namespaces nutzen zu können und so schnell die
Referenzierung der Elemente durchführen zu können, benutz man das Schlüsselwort using. In
dem obigen Beispiel würde die Anweisung ohne der Verwendung von using folgendermaßen
aussehen: System.Console.WriteLine(„Hallo Welt!“); (Vgl. in C++: std::cout ohne
using namespace std;)
5.2 Datentypen
In C# gibt es zwei Arten von Datentypen: Wertetypen (value types) und Verweistypen
(reference types). Wertetypen beinhalten einfache Typen wie z.B. char, int, double, enum
Typen und structs. Verweistypen sind Klassen, Interfaces, Delegaten und Arrays.
Die vordefinierten, nativen Datentypen sollten den meisten aus C/C++ bekannt sein. Bei den
Referenztypen sind nur zwei vordefiniert: object und String. Object ist der Basistyp aller
anderen Typen – insofern werden „primitive“ Typen wie z.B. int auch als Objekte behandelt.
Der String-Typ repräsentiert den Wert eines Unicode Zeichenstrings.
Den Unterschied zwischen den Werte- und Verweistypen kann man im folgenden Programm
verdeutlichen:
using System;
class Class1 {
public int Value = 0;
}
class Test {
static void Main() {
int val1 = 0;
int val2 = val1;
val2 = 123;
//<- nur val2 wird verändert
Class1 ref1 = new Class1();
Class1 ref2 = ref1;
ref2.Value = 123;
//<- auch ref1 betroffen
Console.WriteLine("Values: {0}, {1}", val1, val2);
Console.WriteLine("Refs: {0}, {1}", ref1.Value, ref2.Value);
}
}
Die Ausgabe:
Values: 0, 123
Refs: 123, 123
Während es bei Wertetypen - Variablen nur eigene Werte verändert werden, führt eine
Änderung der Verweistyp – Variable auch zur Aktualisierung der referenzierten Variable.
An dem Beispiel sieht man auch wie die Ausgabe der eigentlichen Werte erfolgt.
In der folgenden Tabelle sind alle vordefinierten Datentypen aufgelistet.
Quelle: ECMA-334
5.2.1 Typumwandlung
Die Umwandlung der Typen geschieht wie in C/C++ entweder implizit oder explizit, wobei
man immer beachten muss, dass beim expliziten Umwandeln es leicht zum Datenverlust
kommen kann. Die nachfolgenden Beispiele verdeutlichen es:
using System;
class Test {
static void Main() {
int intValue = 123;
long longValue = intValue;
Console.WriteLine("{0}, {1}", intValue, longValue);
}
}
Ausgabe: 123,123
-> implizite Konvertierung von int nach long stellt kein Problem dar.
Im Gegensatz dazu ein Beispiel mit expliziter Umwandlung:
using System;
class Test {
static void Main() {
long longValue = Int64.MaxValue;
int intValue = (int) longValue;
Console.WriteLine("(int) {0} = {1}", longValue, intValue);
}
}
Ausgabe: (int) 9223372036854775807 = -1
Der Grund dafür ist klar – ein Überlauf.
Der cast - Operator darf sowohl bei impliziter als auch expliziter Umwandlung verwendet
werden.
5.2.2 Arrays
Arrays können in C# ein- und mehrdimensional sein, „Rechteckige“ und verzweigte Arrays
werden unterstützt. Eine kurze Übersicht der möglichen Array-Ausdrücke:
class Test {
static void Main() {
int[] a1 = new int[] {1, 2, 3};
int[,] a2 = new int[,] {{1, 2, 3}, {4, 5, 6}};
int[,,] a3 = new int[10, 20, 30];
int[][]
j2[0] =
j2[1] =
j2[2] =
//eindimensional, int
//zweidimensional, int
//dreidimensional, int
j2 = new int[3][];
//array von (array von int)
new int[] {1, 2, 3};
new int[] {1, 2, 3, 4, 5, 6};
new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
}
}
5.2.3 Vereinheitlichung der Datentypen
Wie schon erwähnt werden alle Datentypen von der Klasse Object abgeleitet. Demnach ist es
möglich die Methoden dieser Klasse auf einfache Weise aufzurufen.
using System;
class Test {
static void Main() {
Console.WriteLine(3.ToString());
}
}
Hier wird der Wert „3“ vom Typ int mit der Methode der Object Klasse einfach in ein String
umgewandelt und ausgegeben.
Es gibt darüber hinaus eine Möglichkeit der Umwandlung zwischen den Wertetypen und
Referenztypen. Das Verfahren „boxing“ fällt eher in die Kategorie der Typumwandlung, zeigt
aber wiederum die Vereinheitlichung der Datentypen:
class Test {
static void Main() {
int i = 123;
object o = i;
// boxing
int j = (int) o;
// unboxing
}
}
Beim „boxing“ Verfahren wird der Wert bildlich „in eine Box gesteckt“ (Objekt), braucht
man dann wieder den Wert selbst, verwendet man „unboxing“, um ihn aus der Box
herauszuholen.
5.3 Variablen und Parameter
Die Variablen sind wie in allen prozedualen Sprachen Speicherplätze für die Werte. Alle
Variablen sind in C# lokal, d.h. deklarierbar nur in Methoden, Eigenschaften und Indizierern.
Beispiel:
int i;
class Test {
static void Main() {
int a;
int b = 1;
int c,d,e,f;
}
}
//<- nicht erlaubt, da global
Variablen werden als Felder (fields) bezeichnet, wenn sie mit einer Klasse oder Struktur
assoziiert sind. Mit dem Modifizierer static versehenen Felder definieren statische
Variablen (Zugreifbar über Klasse), Felder ohne diesen Modifizierer definieren
Instanzvariablen (Zugreifbar über eine Instanz der Klasse).
using Personnel.Data;
class Employee {
private static DataSet ds;
public string Name;
public decimal Salary;
…
}
Formale Parameter der Methoden definieren ebenso Variablen, die von einem der vier Arten
sein können: Wertparameter (value parameters), Referenzparameter (reference parameters),
Ausgabeparameter (output parameters) oder Parameterarrays (parameter arrays).
Bei dem Werteparameter wird ein Wert direkt an die Methode übergeben, um genauer zu sein
eine Kopie – es findet keine Veränderung außerhalb der Methode statt. Ein
Referenzparameter (ref) dagegen definiert keine Variable, er beinhaltet lediglich eine
Referenz auf eine bereits deklarierte und initialisierte Variable. Die Veränderung der über den
Referenzparameter übergebenen Variable ändert auch die referenzierte Variable außerhalb der
Methode.
using System;
class Test {
static void Swap(ref int a, ref int b) {
int t = a;
a = b;
b = t;
}
static void Main() {
int x = 1;
int y = 2;
Console.WriteLine("pre: x = {0}, y = {1}", x, y);
Swap(ref x, ref y);
//<-beachte - auch beim Aufruf - ref
Console.WriteLine("post: x = {0}, y = {1}", x, y);
}
}
Ausgabe:
pre: x = 1, y = 2
post: x = 2, y = 1
Ausgabeparameter (out) sind Referenzparameter, nur müssen Sie vor der Übergabe an eine
Methode nicht unbedingt initialisiert werden.
using System;
class Test {
static void Divide(int a, int b, out int result, out int remainder) {
result = a / b;
remainder = a % b;
}
static void Main() {
for (int i = 1; i < 10; i++)
for (int j = 1; j < 10; j++) {
int ans, r;
Divide(i, j, out ans, out r); //<-beachte - auch beim Aufruf - out
Console.WriteLine("{0} / {1} = {2}r{3}", i, j, ans, r);
}
}
}
Mit Parameterarrays (params) können mehrere Parameter vom gleichen Typ übergeben
werden, dabei muss beachtet werden, dass ein Parameterarray immer als letzter in der Liste
der Methodenparametern stehen muss, und dass es nur ein einziger eindimensionaler Array
als Parameter agieren darf.
using System;
class Test {
static void F(params int[] args) {
Console.WriteLine("# of arguments: {0}", args.Length);
for (int i = 0; i < args.Length; i++)
Console.WriteLine("args[{0}] = {1}", i, args[i]);
}
static void Main() {
F();
F(1, 2, 3);
F(new int[] {1, 2, 3, 4});
}
}
Ausgabe:
# of arguments: 0
# of arguments: 3
args[0] = 1
args[1] = 2
args[2] = 3
# of arguments: 4
args[0] = 1
args[1] = 2
args[2] = 3
args[3] = 4
5.4 Automatische Speicherverwaltung
Wie in Java gibt, wird bei C# ein Garbage-Collector eingesetzt, der automatisch für die
Speicherbereinigung sorgt und nicht mehr benötigte Objekte selbst löscht. Bei seiner Arbeit
kann er auch andere Objekte im Speicher bewegen. Es ist aber trotzdem noch möglich, mit
Zeigern wie in C++ zu arbeiten. Dazu müssen die entsprechenden Objekte im Speicher fixiert
werden, um zu verhindern, dass sie vom Garbage-Collector von ihrem ursprünglichen
Speicherort verschoben werden. Der Code mit Zeigern muss mit dem Schlüselwort unsafe
markiert werden und wird in C# als „unsicher“ bezeichnet.
using System;
class Test {
static void WriteLocations(byte[] arr) {
unsafe {
// unsicheren Code ankündigen
fixed (byte* pArray = arr) {
// Objekt fixieren
byte* pElem = pArray;
for (int i = 0; i < arr.Length; i++) {
byte value = *pElem;
Console.WriteLine("arr[{0}] at 0x{1:X} is {2}",
i, (uint)pElem, value);
pElem++;
}
}
}
}
static void Main() {
byte[] arr = new byte[] {1, 2, 3, 4, 5};
WriteLocations(arr);
}
}
5.5 Ausdrücke
C# besitzt unäre, binäre und ternäre Operatoren, die Priorität der Auswerung ist absteigend in
der unteren Tabelle angeordnet. Bei mehreren Operatoren mit gleicher Priorität in einem
Ausdruck, gilt für den Zuweisungsoperator und dem Bedingungsoperator (?:)
rechtsassoziative, bei allen andern linksassoziative Auswertung.
Beispiel:
x + y + z wird ausgewertet wie (x + y) + z.
x = y = z wird ausgewertet wie x = (y = z).
Prioritäten der Operatoren absteigend sortiert:
Quelle: ECMA-334
5.6 Anweisungen (Statements)
Die meisten Anweisungen wie for, while, if etc. entsprechen ihren Pedanten aus C/C++. Die
in C# neu eingeführten sind hier aufgelistet.
Labeled statements und
goto statements
foreach statements
throw statements und
try statements
checked und unchecked
statements
lock statements
using statements
static void Main(string[] args) {
if (args.Length == 0)
goto done;
System.Console.WriteLine(args.Length);
done:
System.Console.WriteLine("Done");
}
static void Main(string[] args) {
foreach (string s in args)
System.Console.WriteLine(s);
}
static int F(int a, int b) {
if (b == 0)
throw new Exception("Divide by zero");
return a / b;
}
static void Main() {
try {
System.Console.WriteLine(F(5, 0));
}
catch(Exception e) {
Console.WriteLine("Error");
}
}
static void Main() {
int x = Int32.MaxValue;
System.Console.WriteLine(x + 1); // Overflow
checked {
System.Console.WriteLine(x + 1); // Exception
}
unchecked {
System.Console.WriteLine(x + 1); // Overflow
}
}
static void Main() {
A a = …;
lock(a) {
a.P = a.P + 1;
}
}
static void Main() {
using (Resource r = new Resource()) {
r.F();
}
}
Man beachte, dass die goto Anweisung aus Basic in C# übernommen wurde.
5.7 Klassen
Klassendeklarationen definieren neue Referenztypen. Eine Klasse kann erben und kann
Interfaces implementieren. In C# ist es wie in Java keine Mehrfachvererbung möglich,
Implementierung mehrerer Interfaces dagegen schon.
Klassenmember können Konstanten, Felder, Methoden, Eigenschaften, Ereignisse,
Indizierer, Operatoren, Instanzkonstruktoren, Destruktoren, statische Konstruktoren
und geschachtelte Typen (nested types) sein.
Jedes der Klassenmember hat eigene Zugriffsmodifizierer. Es gibt fünf mögliche Formen der
Zugriffsberechtigungen, die in der Tabelle zusammengefasst sind.
public
protected
Uneingeschränkter Zugriff
Zugriff ist auf die enthaltene Klasse oder auf Typen
begrenzt, die von der enthaltenen Klasse abgeleitet
sind
internal
Zugriff ist auf dieses Programm begrenzt
protected internal Zugriff ist auf dieses Programm oder auf Typen
begrenzt, die von der enthaltenden Klasse abgeleitet
sind
private
Der Zugriff ist auf den enthaltenden Typ begrenzt
5.7.1 Konstanten (constants)
Eine Konstante ist ein Klassenmember und repräsentieren einen konstanten Wert, d. h. einen
Wert, der während der Kompilierung berechnet werden kann. Die Konstanten brauchen kein
static Attribut, es wird auch nicht zugelassen. Der Zugriff auf Konstanten kann bei
entsprechendem Modifizierer public auch von außerhalb der Klasse erfolgen.
5.7.2 Felder (fields)
Ein Feld ist ein Klassenmember, der eine mit einem Objekt oder einer Klasse verknüpfte
Variable repräsentiert. Einfacher gesagt sind es Variablen, die auf der Klassenebene deklariert
sind.
5.7.3 Methoden (methods)
Eine Methode ist ein Klassenmember, der die Berechnungen und Aktionen, die von der
Klasse oder einem Objekt ausgeführt werden können, implementiert. Methoden haben eine
Parameterliste (eventuell eine leere), einen Rückgabewert (void = kein Rückgabewert) und
können entweder statisch oder nicht-statisch sein. Statische Methoden sind über die Klasse
zugreifbar, nicht-statische nur über eine Instanz der Klasse – also über ein Objekt.
using System;
public class Stack {
public static Stack Clone(Stack s) {…}
public static Stack Flip(Stack s) {…}
public object Pop() {…}
public void Push(object o) {…}
public override string ToString() {…}
...
}
Methoden können überladen werden – die Methoden dürfen den gleichen Namen haben,
solange sie ihre eigene Signaturen haben. Die Signatur einer Methode besteht aus dem Namen
der Methode sowie der Anzahl, den Modifizierern und den Typen der formalen Parameter.
Beispiel zeigt eine Klasse mit mehreren Methoden namens F:
using System;
class Test {
static void F() {
Console.WriteLine("F()");
}
static void F(object o) {
Console.WriteLine("F(object)");
}
static void F(int value) {
Console.WriteLine("F(int)");
}
static void F(ref int value) {
Console.WriteLine("F(ref int)");
}
static void F(int a, int b) {
Console.WriteLine("F(int, int)");
}
static void F(int[] values) {
Console.WriteLine("F(int[])");
}
static void Main() {
F();
F(1);
int i = 10;
F(ref i);
F((object)1);
F(1, 2);
F(new int[] {1, 2, 3});
}
}
Ausgabe:
F()
F(int)
F(ref int)
F(object)
F(int, int)
F(int[])
5.7.4 Eigenschaften (properties)
Eine Eigenschaft ist ein Klassenmember, welcher den Zugang zu Merkmalen (Eigenschaften)
eines Objekts oder einer Klasse liefert. Eigenschaften sind die natürliche Erweiterung von
Feldern. Beide sind benannte Member mit verknüpften Typen und die Syntax für den Zugriff
auf Felder und Eigenschaften ist identisch. Im Unterschied zu Feldern kennzeichnen
Eigenschaften jedoch keine Speicherorte. Stattdessen besitzen Eigenschaften
Zugriffsmechanismen (accessors), die die Anweisungen angeben, die beim Lesen bzw.
Schreiben ihrer Werte auszuführen sind.
Beispiel:
public class Button {
private string caption;
public string Caption {
get {
return caption;
}
set {
caption = value;
Repaint();
}
// Feld
// Eigenschaft
}
...
}
Eigenschaften, die sowohl geschrieben als auch gelesen werden können, haben get und set
Accessoren. Der Accessor get wird aufgerufen sobald ein Lesezugriff auf die Eigenschaft
erfolgt, set – wenn es sich um einen Schreibzugriff handelt. Das Lesen und Schreiben erfolgt
wie bei normalen Feldern.
Button b = new Button();
b.Caption = "ABC"; // set; Repaint();
string s = b.Caption; // get;
b.Caption += "DEF"; // get & set; Repaint();
5.7.5 Ereignisse (events)
Ein Ereignis (event) ist ein Klassenmember, der einem Objekt oder einer Klasse das
Bereitstellen von Benachrichtigungen ermöglicht. Die Deklaration einer Eigenschaft sieht wie
die Deklaration eines Feldes mit dem vorangestellten Schlüsselwort event aus. Der Typ einer
Ereignis - Deklaration muss ein Delegat sein.
In dem Beispiel
public delegate void EventHandler(object sender, System.EventArgs e);
public class Button {
public event EventHandler Click;
public void Reset() {
Click = null;
}
}
definiert die Klasse Button ein Click-Ereignis vom Typ EventHandler.
Die Verwendung der Ereignisse kann so aussehen:
using System;
public class Form1 {
public Form1() {
Button1.Click += new EventHandler(Button1_Click);
}
Button Button1 = new Button();
void Button1_Click(object sender, EventArgs e) {
Console.WriteLine("Button1 was clicked!");
}
public void Disconnect() {
Button1.Click -= new EventHandler(Button1_Click);
}
}
Näheres wird im ECMA – Standard beschrieben.
5.7.6 Operatoren (operators)
Ein Operator ist ein Member, der die Bedeutung eines Ausdrucksoperators definiert, der für
die Instanzen der Klasse angewendet werden kann. Es gibt drei Kategorien von Operatoren:
unäre Operatoren, binäre Operatoren und Konvertierungsoperatoren.
Der folgende Beispiel definiert einen Digit Typ, der Dezimalwerte zwischen 0 und 9
repräsentiert.
using System;
public struct Digit {
byte value;
public Digit(byte value) {
if (value < 0 || value > 9) throw new ArgumentException();
this.value = value;
}
public Digit(int value): this((byte) value) {}
public static implicit operator byte(Digit d) {
return d.value;
}
public static explicit operator Digit(byte b) {
return new Digit(b);
}
public static Digit operator+(Digit a, Digit b) {
return new Digit(a.value + b.value);
}
public static Digit operator-(Digit a, Digit b) {
return new Digit(a.value - b.value);
}
public static bool operator==(Digit a, Digit b) {
return a.value == b.value;
}
public static bool operator!=(Digit a, Digit b) {
return a.value != b.value;
}
public override bool Equals(object value) {
if (value == null) return false;
if (GetType() == value.GetType()) return this == (Digit)value;
return false;}
public override int GetHashCode() {
return value.GetHashCode();
}
public override string ToString() {
return value.ToString();
}
}
class Test {
static void Main() {
Digit a = (Digit) 5;
Digit b = (Digit) 3;
Digit plus = a + b;
Digit minus = a - b;
bool equals = (a == b);
Console.WriteLine("{0} + {1} = {2}", a, b, plus);
Console.WriteLine("{0} - {1} = {2}", a, b, minus);
Console.WriteLine("{0} == {1} = {2}", a, b, equals);
}
}
Der Digit Typ definiert folgende Operatoren:
• Einen Operator für die implizite Umwandlung von Digit in byte.
• Einen Operator für die explizite Umwandlung von byte in Digit.
• Einen Plus-Operator zum Addieren von zwei Digit Werten, gibt einen Digit Wert zurück.
• Einen Minus-Operator zum Subtrahieren eines Digit Wertes vom andern, gibt einen Digit
Wert zurück.
• Einen Gleichheits- (==) und Ungleichheits- (!=) Operator, der zwei Digit Werte vergleicht.
5.7.7 Indizierer (indexers)
Ein Indizierer ist ein Member, der es einem Objekt ermöglicht, auf die gleiche Art wie ein
Array indiziert zu werden. Während Eigenschaften einen Feld-ähnlichen Zugriff ermöglichen,
bieten Indizierer einen Array-ähnlichen Zugriff.
Ein entsprechendes Beispiel zeigt, wie bei den Indizierern die von den Eigenschaften
bekannten Mechanismen get und set verwendet werden. Der indizierender Parameter befindet
sich in den eckigen Klammern.
using System;
public class Stack {
private Node GetNode(int index) {
Node temp = first;
while (index > 0) {
temp = temp.Next;
index--;
}
return temp;
}
public object this[int index] {
get {
if (!ValidIndex(index))
throw new Exception("Index out of range.");
else
return GetNode(index).Value;
}
set {
if (!ValidIndex(index))
throw new Exception("Index out of range.");
else
GetNode(index).Value = value;
}
}
...
}
class Test {
static void Main() {
Stack s = new Stack();
s.Push(1);
s.Push(2);
s.Push(3);
s[0] = 33; // Oberes Element: 3 -> 33
s[1] = 22; // Mittleres Element: 2 -> 22
s[2] = 11; // Unteres Element: 1 -> 11
}
}
5.7.8 Instanzkonstruktoren (instance constructors)
Ein Instanzkonstruktor ist ein Member, der die zur Initialisierung einer Klasseninstanz
erforderlichen Aktionen implementiert.
using System;
class Point {
public double x, y;
public Point() {
this.x = 0;
this.y = 0;
}
public Point(double x, double y) {
this.x = x;
this.y = y;
}
...
}
Die Klasse Point hat zwei Instanzkonstruktoren – einen leeren und einen, der zwei
Argumente erwartet.
Die eigentliche Verwendung wird im folgenden Code deutlich:
class Test {
static void Main() {
Point a = new Point();
Point b = new Point(3, 4);
…
}
}
je nach Aufruf wird der entsprechende Instanzkonstruktor aufgerufen. Falls eine Klasse
keinen Instanzkonstruktor definiert, wird automatisch ein Instanzkonstruktor ohne Argumente
zur Verfügung gestellt.
5.7.9 Destruktoren (destructors)
Ein Destruktor ist ein Member, der die Aktionen implementiert, die zum Zerstören einer
Klasseninstanz erforderlich sind. Destruktoren erlauben keine Argumente, sie besitzen keine
Zugriffsmodifizierer und können nicht explizit aufgerufen werden. Der Destruktor einer
Klasseninstanz wird automatisch aufgerufen, wenn der Garbage-Collector tätig wird.
Die Klasse Point wird um den Destruktor erweitert:
using System;
class Point {
public double x, y;
public Point() {
this.x = 0;
this.y = 0;
}
public Point(double x, double y) {
this.x = x;
this.y = y;
}
~Point() {
Console.WriteLine("Destructed {0}", this);
}
...
}
5.7.10 Statische Konstruktoren (static constructors)
Ein statischer Konstruktor ist ein Member, der die Aktionen implementiert, die zum
Initialisieren einer Klasse erforderlich sind. Statische Konstruktoren erlauben keine
Argumente, sie besitzen keine Zugriffsmodifizierer und können nicht explizit aufgerufen
werden. Sie werden automatisch aufgerufen, sobald eine Instanz der Klasse erstellt wird.
Beispiel:
using Personnel.Data;
class Employee {
private static DataSet ds;
static Employee() {
ds = new DataSet(…);
}
public string Name;
public decimal Salary;
…
}
Sobald eine Instanz der Klasse Employee erstellt wird, wird der statische Konstruktor
aufgerufen und legt einen neuen Datensatz vom Typ DataSet an.
5.7.11 Vererbung (inheritance)
Alle Klassen unterstützen Einfachvererbung und der Typ object ist die ultimative
Basisklasse für alle Klassen. Alle Klasse, die in frühen Beispielen vorgestellt wurden, sind
von object abgleitet.
using System;
class A {
public void
}
class B: A {
public void
}
class Test {
static void
B b = new
b.F();
b.G();
}
}
F() { Console.WriteLine("A.F"); }
G() { Console.WriteLine("B.G"); }
Main() {
B();
// geerbt von A
// enthalten in B
Klasse A ist von object abgeleitet , Klasse B dagegen von A.
5.8 Strukturen (structs)
Strukturen sind den Klassen sehr ähnlich, sie können auch Interfaces implementieren und
ebenso die gleichen Memeber wie die Klassen besitzen. Strukturen unterschieden sich jedoch
in einigen wichtigen Punkten von den Klassen:
• Strukturen sind Wertetypen, Klassen sind dagegen Referenztypen
• Vererbung ist bei Strukturen nicht möglich
Einen „Punkt“ kann man als eine Klasse oder eine Struktur realisieren.
Als Klasse:
class Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Test {
static void Main() {
Point[] points = new Point[100];
for (int i = 0; i < 100; i++)
points[i] = new Point(i, i*i);
}
}
Als Struktur:
struct Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Man verwendet in der Regel Strukturen immer dann, wenn ein Objekt gewünscht wird, das
sich wie ein einfacher Datentyp verhält, d.h. ohne viele Ressourcen auskommt und schnell
zugeordnet werden kann.
5.9 Schnittstellen (interfaces)
Eine Schnittstelle definiert einen Vertrag. Eine Klasse oder Struktur, die eine Schnittstelle
implementiert, muss ihren Vertrag einhalten. Schnittstellen können Methoden,
Eigenschaften, Ereignisse und Indizierer als Member enthalten.
Beispiel für ein Interface mit einem Indizierer, einem Ereignis E, einer Methode F und einer
Eigenschaft P:
interface Iexample {
string this[int index] { get; set; }
event EventHandler E;
void F(int value);
string P { get; set; }
}
public delegate void EventHandler(object sender, EventArgs e);
Bei den Schnittstellen ist eine Mehrfachvererbung möglich:
interface Icontrol {
void Paint();
}
interface ITextBox: Icontrol {
void SetText(string text);
}
interface IListBox: Icontrol {
void SetItems(string[] items);
}
interface IComboBox: ITextBox, IListBox {}
Eine Klasse kann auch mehrere Interfaces implementieren:
interface IDataBound{
void Bind(Binder b);
}
public class EditBox: Control, IControl, IdataBound {
public void Paint() {…}
public void Bind(Binder b) {…}
}
5.10 Delegaten (delegate)
Delegaten ermöglichen Szenarien, für die in anderen Sprachen Funktionszeiger verwendet
wurden. Delegaten sind im Gegensatz zu Funktionszeigern in C++ vollständig objektorientiert
und sind typensicher.
Die Verwendung von Delegaten erfolgt in drei Schritten: Deklaration, Instanziierung und
Aufruf. Dies wird am folgenden Beispiel verdeutlicht:
delegate void SimpleDelegate();
class Test {
static void F() {
System.Console.WriteLine("Test.F");
}
static void Main() {
SimpleDelegate d = new SimpleDelegate(F);
d();
}
}
// Deklaration
// Instanziierung
// Aufruf
Eine Delegatdeklaration definiert eine Klasse, die von der System.Delegate-Klasse
abgeleitet ist. Eine Delegatinstanz enthält eine Aufrufliste mit einer oder mehreren Methoden,
auf die verwiesen wird. Beim Aufruf einer Delegatinstanz mit einem entsprechenden Satz von
Argumenten werden alle aufrufbaren Methoden des Delegaten mit dem vorgegebenen Satz
von Argumenten aufgerufen.
Eine interessante und nützliche Eigenschaft einer Delegatinstanz ist die Tatsache, dass sie die
Klassen der Methoden, die sie einschließt, nicht kennt oder sich nicht dafür interessiert. Die
einzige Voraussetzung besteht darin, dass die Methoden mit dem Typ des Delegaten
kompatibel sind. Das macht Delegaten perfekt für "anonyme" Aufrufe geeignet.
5.11 Enumerationen (enums)
Ein Enumerationstyp ist ein bestimmter Werttyp, der eine Reihe von benannten Konstanten
deklariert. Die Enumerationen werden meist zur besseren Lesbarkeit des Quelltexts
verwendet. Außerdem können „intelligente“ Editoren anhand von enums die möglichen Werte
bei ihrer Verwendung vorschlagen.
Ein Beispiel zur Deklaration und Verwendung von Enumerationen:
enum Color {
Red,
Blue,
Green
}
class Shape {
public void Fill(Color color) {
switch(color) {
case Color.Red:
…
break;
case Color.Blue:
…
break;
case Color.Green:
…
break;
default:
break;
}
}
}
5.12 Namespaces und Assemblies
C# - Programme werden mit Hilfe von Namespaces organisiert. Sie werden sowohl als
"internes" Organisationssystem für ein Programm als auch als "externes" Organisationssystem
verwendet. Das externe System bietet eine Möglichkeit, Programmelemente darzustellen, die
für andere Programme offen gelegt werden.
Assemblies werden zur physikalischen Trennung der Programmkomponenten in Baugruppen
verwendet. Ein Assembly kann Typen, ausführbaren Code für die Implementierung dieser
Typen und Referenzen zu anderen Assemblies enthalten.
Um das Prinzip der Trennung zu demonstrieren, wird das schon bekannte Programm „Hallo
Welt“ in zwei Teile gesplittert: eine Klassenbibliothek, die die Nachricht bereitstellt und eine
Konsolenanwendung, die diese Nachricht anzeigt.
Die Klassenbibliothek wird eine Klasse HelloMessage enthalten.
// HelloLibrary.cs
namespace CSharp.Introduction {
public class HelloMessage {
public string Message {
get {
return "Hallo Welt!";
}
}
}
}
//neuer Namespace wird definiert
//Eigenschaft der Klasse (read-only)
Als Nächstes wird die Anwendung erstellt, die diese Klasse verwendet. Der Zugriff auf die
Klasse erfolgt normalerweise über CSharp.Introduction.HelloMessage, doch man kann
die using namespace Direktive verwenden um das Ganze zu verkürzen, wie es bei den
Beispielen mit der Ausgabe auf der Konsole schon gezeigt wurde.
// HelloApp.cs
using CSharp.Introduction;
//Verwendung vom Namespace
CSharp.Introduction
class HelloApp {
static void Main() {
HelloMessage m = new HelloMessage();
System.Console.WriteLine(m.Message);
}
}
Jetzt kann der Quellcode kompiliert werden. Und zwar zum einen in eine Klassenbibliothek
und zum andern in die Konsolenanwendung mit der Klasse HelloApp.
Beim .NET-Framework kann das durch folgende Anweisungen geschehen:
csc /target:library HelloLibrary.cs
csc /reference:HelloLibrary.dll HelloApp.cs
Als Ergebnis wird eine Klassenbibliothek namens HelloLibrary.dll und eine Anwendung
namens HelloApp.exe erstellt.
5.13 Versioning
Beim Versioning handelt es sich um den Prozess der Verfolgung einer Komponente, um die
Kompatibilität der Komponente sicherzustellen. Eine neue Version einer Komponente ist
quellenkompatibel mit einer vorherigen Version, wenn der von der alten Version abhängige
Code nach der erneuten Kompilierung auch in der neuen Version funktioniert. Im Gegensatz
dazu ist die neue Version einer Komponente binärkompatibel, wenn die von der alten Version
anhängige Anwendung ohne erneute Kompilierung mit der neuen Version funktioniert.
Ein ausführliches Beispiel ist in dem ECMA-334 Dokument enthalten.
5.14 Attribute (attributes)
Ein großer Teil der Sprache C# ermöglicht dem Programmierer das Festlegen von
Deklarationsinformationen zu den im Programm definierten Elementen. So wird
beispielsweise die Zugriffsmöglichkeit einer Methode in einer Klasse durch die Ergänzung
von Methodenmodifizierern wie public, protected, public, protected oder internal festgelegt.
C# ermöglicht Programmierern auch die Einführung neuer Arten von
Deklarationsinformationen, die als Attribute bezeichnet werden. Programmierer können dann
diese Attribute mit verschiedenen Programmelementen verbinden und in einer
Laufzeitumgebung Attributinformationen abrufen. Eins wohl der wichtigsten Attribute ist
sysimport. Damit ist es möglich, die in systemeigenem Code geschriebene Funktionen
aufzurufen. Der Typ und der Name der Datei werden als Parameter des sysimport-Attributs
angegeben.
Das Beispiel zeigt einen Aufruf der „windowseigenen“ Messagebox:
class Test
{
[sysimport(dll="user32.dll")]
public static extern int MessageBoxA(int h, string m,
string c, int type);
public static void Main()
{
int ret = MessageBoxA(0, "Hallo Welt!", "Caption", 0);
}
}
6. Quellen
•
ECMA-334 Standard „C# Language Specification“
http://www.ecma-international.org/publications/standards/ecma-334.htm
•
C# - Programmiersprachespezifikation
http://msdn.microsoft.com/library/deu/default.asp?url=/library/DEU/csspec/html/CSha
rpSpecStart.asp
•
Eric Gunnerson, C# - Tutorial und Referenz
http://www.galileocomputing.de/openbook/csharp/
•
Guide to C#
http://www.golohaas.de/csharp
•
News Archiv von heise.de
http://www.heise.de/newsticker/archiv
Zugehörige Unterlagen
Herunterladen