Zusammen mit dem .NET Framework hat Microsoft die Programmiersprache C# (C-Sharp) entwickelt. Die Sprache wurde stark an C++ angelehnt, die Sprache Visual Basic und Object Pascal (Delphi) nahmen ebenfalls Einfluss. Da auch die Sprache Java von C++ abstammt, sind viele Ideen und Konzepte gemeinsam. Nüchtern betrachtet, ist C# eine Weiterentwicklung von Java. Viele C#-Konzepte sind inzwischen auch zu Java zurückgeflossen.
Lernen Sie elementaren Bestandteile der Programmiersprache C# kennen, können eigene Programme damit entwerfen, erstellen und warten. Nach diesem Kurs haben Sie alle erforderlichen Grundlagen, um sich in fortgeschrittene Themen von .NET einzuarbeiten.
Softwareentwickler, die von einer Sprache wie C++, Delphi, Smaltalk, Java oder einer anderen objektorientierten Sprache herkommen, sich für die Programmierung der .NET-Plattform optimale Voraussetzungen erarbeiten möchten.
Guten Kenntnisse der Programmiersprachen C++, Delphi, Smaltalk oder Java oder sehr gute Kenntnisse in C oder Visual Basic.
.NET ist die aktuelle Entwicklungsplattform für Windows- bzw. Internet-Applikationen von Microsoft.
Sie erhalten in diesenm Kapitel einen groben Überblick über die Bedeutung von .NET. Sie kennen die Hauptelemente, die .NET ausmachen, und kennen die funktionellen Bestandteile der CLR1) (Common Language Runtime).
.NET ist das dritte, komplett neue Entwicklungsmodel in der Microsoft-Geschichte.
Die Entwicklung für .NET begann im Jahre 1998. Die Funktionen von .NET wurden kontinuierlich weiterentwickelt. Seit 2017 ist die Entwicklung von Programmen unterschiedliche Betriebsysteme 6) möglich.
Begriff | Bedeutung |
---|---|
CLR7) | Common Language Runtime. Herzstück des .NET Framework. Ist die Laufzeitumgebung fürdie .NET-Applikationen. |
Memory Management | in diesem Teil wird die gesamte Speicherverwaltung erledigt. Teil des Memory Management sind: Anlegen und Verwalten des Speichers, ROC8) Reference Counting für Objekte, GC9) Garbage Collection |
Common Type System | Das gemeinsame Typen-System ermöglicht die Entwicklung und einfache Interaktion zwischen Programmen, die mit unterschiedlichen Programmiersprachen erstellt worden sind. |
Lifecycle Monitoring | Überwacht die Systemeinheiten wie Programmen, Ressourcen, Objekten usw. |
.NET Framework Base Classes | Basisklassen von .NET. |
ADO.NET10) | Active Data Objects. Eine Gruppe von Klassen, die Datenzugriffsdienste (z.B. auf Datenbanken) zur Verfügung stellt. |
XML11) | Extensible Markup Language. Datenbeschreibungssprache, die in .NET für die Beschreibung und den Austausch von Daten verwendet wird. |
Threading | Klassen, um Multithreading-Applikationen zu ermöglichen. |
IO12) | *Input Output*. Gruppe von Klassen, die Ein- und Ausdgaben unterstützen (z.B. Dateien schreiben und lesen. |
Net | Implementation der gägngigen Netzwerkprotokolle wei TCP/IP. |
Diagnostics | Klassen für das Tracen und Debugen von Applikationen. |
ASP.NET13) | Active Server Pages. Basis für die Implementierung von Internet-Anwendungen. |
Web Services | Stellen Mechanismen zur Verfügung, um über das Internet mittels SOAP14) (Simple Object Access Protocol) zu kommunizieren. Mit WEB-Services können programmierbare Business-Logic-Komponenten auf Webservern zur Verfügung gestellt werden, die von ASP.NET Clients transparent (soll heissen unabhängig von ihrem physikalischen Standpunkt) genutzt werden können. |
Web Form | Umgebung, um eine Web-oberfläche zu erzeugen. |
ASP.NET Application Services15) | Services zu Erstellung von ASP.NET-Applikationen. |
Windows Forms | Plattform zur Erstellung von WIN32-Desktop-Applikationen. Basierent auf der für J++ entwickelten WCF16). |
WPF17) | Windows Presentation Foundation. Eine Plattform zur Erstellung von Desktop-Applikationen die auf dem neuen XAML18)-Deklaration basiert. |
Controls | Eingeständige, grafische Einheit mit Funktionalität für die Erstellung und Erweiterung Oberflächen. |
Drawing | Zeichfunktionalität. |
Windows Application Services | Services zur Erstellung von Windows-Forms-Applikationen. |
WCF19) | Windows Communication Foundation. Entwicklungsklassen und Basis zur Entwicklung SOA20)(service-oriented applications) |
Die .NET Programme basieren auf mehreren .NET Standards und Tools auf.
Der Herstellungsprozess einer Applikation gestaltet sich einfach. Jeder Quellcode wird in eine einheitliche Zwischensprache CIL21) (Common Intermediate Language) übersetzt.
Dabei spielt es keine Rolle ob Ihr Programm in C#, Visual Baisc, J#, F# geschrieben wurde. Alle werden in den identischen in CIL-Code übersetzt. Dieser CIL-Code ist nicht geschützt und kann von jedermann eingesehen werden.
Die betriebsystemabhängige CLR22) (Common Language Runtime) überstetzt den CIL-Code zu Laufzeig in den jeweiligen Maschinencode. Die CLR stellt somit die Verbindung zum Betriebsystem und zur CPU-Code her.
Wie flexibel diese CLR ist zeigt sich an den Beispielen .NET Micro Framework23), Mono24), Windows CE25).
Diese Beispiel einer Console-Anwendung zeigt eine Meldung: „Hallo World“ an und ist direkt in CIL26) geschrieben. Damit entfällt die Übersetzung aus der Hochsprache wie C#.
.assembly Hello {} .assembly extern mscorlib {} .method static void Main() { .entrypoint .maxstack 1 ldstr "Hello, world!" call void [mscorlib]System.Console::WriteLine(string) ret }
Die API27) (Application Programming Interface) des .NET Standard 2.0 beinhaltet folgende Programmierschnittstellen bzw Klassen:
Technologie | Klassen & Schnittstellen |
---|---|
XML | XLinq, XML Document, XPath, Schema XSL |
Serialization | BinaryFormatter, Data Contract XML |
Networking | Sockets, Http, Mail, WebSockets |
IO | Files, Compression, MMF |
Threading | Threads, Thread Pool, Tasks |
Core | Primitives, Collections, Reflection, Interop, Linq |
Wir erstellen unser erstes C#-Programm und diskutieren das Ergebnis.
Wählen Sie im Startfenster [Create New Project] oder wählen Sie unter [File]⇒[New Project], um ein neues C#-Projekt zu erzeugen.
Sie können ein neues Projekt auf der Startseite von Visual Studio 2017 erstellen.
Alternativ können Sie auch über das Menu (wie oben beschirieben) erstellen. Wählen Sie wie auf dem Bild ersichtlich, als Projekttyp eine Windows-Konsoleanwendung aus. Geben Sie anschliessend folgenden C#-Code ein.
namespace HelloWorld { using System; /// <summary> /// Beschreibung der Klasse Programm /// </summary> public class Program { public static int Main(string[] args) { System.Console.WriteLine("HelloWorld"); System.Console.Read(); return 0; } } }
Code | Beschreibung |
---|---|
namespace HelloWorld | Defniert den Namensraum HelloWorld. Hilft beim Organisieren von Codeteilen und ermöglicht systemweite eindeutige Namen. Der Namensraum ist hyrarchisch aufgebaut und kann beliebig tief sein. |
{…} | Begrenzt von Programmblöcken. Blöcke werden durch geschweifte Klammern gebildet. Sie können verschachtelt werden. Mit Blöcken werden zusammengehöriende Programmteile gekenntzeichnet. Blöcke zeigen dem Compiler, wo ein Programmteil beginnt, wo er endet und was alles dazu gehört. |
using system | Definierteinen Aliea Namen im aktuellen Namenspace HelloWorld un dermöglilcht es, die Bestandteile des Namensraums-System zu benutzen, ohne den entsprechenden Spezifizierer anzugeben. In unseren Beispiel hätte wir statt System.Console.WriteLine(„HelloWorld“); schlicht Console.WriteLine(„HelloWorld“); schreiben können, weil der entsprechende Alias angelegt wurde. |
./*…*/ | Komentare. /* bezeichnet den Beginn eines Kommentarblocks, */beendet ihn. Diese Art des Kommentars kann verschachtelt werden. In vielen Programmeditoren werden Kommentare mit einer bestimmten Schriftfarbe angezeigt (Visual Studio Defaultfarbe grün). |
public class Programm | Mit dieser Zeile wird innerhalb unseres Namensraums eine öffentlich ansprechbare Klasse mit dem Namen Programm angelegt. |
public static int Main(string[] args) | Sobalt ein Programm gestartet wird, sucht die Laufzeitumgebung nach dieser Methode. Jedes Programm besitzt im Normalfall eine Main() Funktion, die als static und public definiert werden muss. Der Modifikator static besagt, dass die folgende Methode eine Klassenmethode (im Gegensatz zu Objektmethode) darstellt. Dies wiederum bedeutet: Der Aufruf einer statischen deklarierten Methode kann zu jedem Zeitpunkt erfolgen, ohne das eine Instanz der Klasse zur Verfügung stehen muss. In Spezialfällen können mehr als eine Main() Methode in einer Applikation angeboten werden (jedoch höchstens eine pro Klasse); In diesem Fall muss dem Compiler angegeben werden, welche Main Methode beim Start angespochen werden soll. (csc /main/:HelloWorld HelloWorld.cs). Als Parameter kann für die Main-Funktion ein String Array mit dem namen args mitgegeben werden. Dieses String Array dient zur Bearbeitung von Parameter, die beim Starten eines Programms mitgegeben werden können. Auch hierzu später mehr. |
System.Console.WriteLine(„HelloWorld“); | Aus dem Namensbereich System wird die Mehode WriteLine der Console aufgerufen. Als Parameter wird hier eine (hartcodierte) Zeichenkette ausgegeben. Abgeschlossen werden C#-Befehle immer mit einem Strichpunkt. Es ist daher möglich, mehrere Anweisungen in einer Zeile anzugeben (wird nicht empfohlen) sowie eine Anweiseung auf mehrere Zeilen zu verteilen. |
return 0; | Beendet die Ausführung der Mehtode Main() und gibt den Wert 0 zurück. |
C# unterscheidet Gross/Kleinschreibung.
Wir haben in diesem Kapitel gesehen, wie wir mit dem Application Wizard ein Programm im Visual Studio .NET erzeugten können. Wir haben den erzeugten Applikations-Rumpf einer Konsolen-Applikation betrachtet und ihn um die Funktionalitäten Ausgabe eines Strings auf der Konsole und warten auf eine Eingabe erweitert. Anschliessend habe wir die einzelnen Elemente der Applikation „HelloWorld“ besprochen.
Warum ist die Methode Main() so wichtig für ein Programm? |
Was bedeutet das Wort static? |
Welche Arten von Kommentaren gibt es? |
Für die Programmentwicklung ist eine Programmstrukturierung schon bei kleineren, mit Sicherheit jedoch bei mittleren bis grossen Projekten, unumgänglich. C# bietet hier einige Möglichkeiten an, die in diesem Kapitel behandelt werden.
Sie lernen in diesem Kapitel folgendes kennen: Klassen und Objekte, Felder einer Klasse, Methoden einer Klassen, Namensräume |
Zuerst ein paar wichtige Begriffsdefinitionen, auf denen wir im weiteren Verlauf aufbauen werden.
Klasse | Eine Klasse ist ein Bauplan zur Erzeugung konkreter Objekte. Sie bestehen aus Attributen (Eigenschaften) und Methoden (Verhaltensweisen). Eine Klasse entspricht dem Datentyp eines Objekts. |
Objekt oder auch Instanz einer Klasse | Sind Instanzierungen von Klassen. Wenn ein Objekt erzeugt wird, wird dynamisch Speicher für diese Objekt angelegt, der irgendwann wieder freigegeben werden muss. In C# müssen wir uns nit mehr explizit um die Freigabe des durch Objekte reservierten Speicher kümmern, das erledigt die GC28) (Garbage Collection). |
Member einer Klasse | Sammelbegriff für die Attribute und Methoden einer Klasse |
Methoden oder auch Memberfunktionen einer Klasse | Funktionalität einer Klasse (definieren Verhalten). |
Die Klassendeklaration besteht aus dem Namen der Klasse, den Feldern und der Methode Klasse. Eine typische Klassendeklaration sehen wir im Folgenden. Dabei wird innerhalb des Namensraums RentCar die Klasse Vehicle angelegt.
namespace RentCar { public class Vehicle { // Class Implementation } }
Warum braucht man überhaupt Instanzierungen von Klassen?
Das kommt daher, dass aus objektorientierter Sicht eine Klasse von der Idee her lediglich eine Art Schablone für konkrete Objekte darstellt. Nehmen wir als Beispiel die Klasse Vehicle: Will ich mit dieser Klasse z.B. ein Objekt erzeugen, das einem Auto entspricht, lege ich ein Objekt an und fülle die Felder entsprechend den Eigenschaften eines Autos ab. Das bedeutet, das Objekt Auto ist erst nach der Instanzierung und der entsprechenden Initialierung für das Programm verfügbar.
Die Anweisung, um ein Motorraf und zwei Auto-Objekte anzulegen, sehen dann folgender Massen aus:
namespace RentCar { using System; /// <summary> /// Summary description for GarageMain. /// </summary> public class GarageMain { public static int Main(string[] args) { Vehicle vehicle1 = new Vehicle("Fahrrad"); Vehicle vehicle2 = new Vehicle("Motorrad"); Vehicle vehicle3 = new Vehicle("Auto"); Vehicle vehicle4 = new Vehicle("Auto"); return 0; } } public class Vehicle { private string _name; // Konstruktor public Vehicle(string name) { _name = name; } } }
Code | Bedeutung |
---|---|
new | Mit dem reservierten Wort new werden Instanzen einer Klasse erzeugt, spricht Objekte der Klasse angelegt und initialisiert. Die Anweisung bedeutet für den Compiler: Erzeuge eine Kopie des noachfolgenden Datentyps im Speicher meines Computers! |
Sie erkennen in vorangehenden Beispiel, dass von einer Klasse häufig mehrere Objekte angelegt werden, die sich durch unterschiedlich abgefüllten Felder unterscheiden, die die Eingeschaften eines Objektes repräsentieren.
In C# unterscheidet man drei Arten von Variablen:
Felder sind nichts anderes als Variablen oder Konstanten, die innerhalb der Klasse deklariert werden und auf die über ein Objeekt zugegriffen werden kann. Felder entsprechen also den Objektdaten, die den Zustand eines Objekts speichern.
Syntax: [Modifikatoren] Datentyp Bezeichner [=Initialwert]
Beispiel:
public string _firstname = „Frank“;
private int _nrOfEntry = 1;
Notation: [ ] eckige Klammern bezeichnen optimale Teile einer Syntax. Bedeutet in obigen Beispiel: Ein Modifikator kann, muss aber nicht vor dem Datentyp stehen.
Gross- Kleinschreibung: C# ist Case-sensitiv, das heisst alleMitarbeiter und AlleMitarbeiter sind für C# unterschiedliche Bezeichner.
Der Datentyp int ist ein Alias für den im Namensraum-System definierten Basistyp Int32. Die Datentypen sind in Kapitel beschrieben.
Initialisierung: Jede Variable muss von der ersten Benutzung initialisiert werden.
Beispiele für gültige Bezeichner:
Beispiele für ungültige Bezeicher:
Der Programmierer beeinflusst mit Modifikatioren die Sichtbarkeit und das Verhalten von Variablen, Konstanten, Methoden und Klassen oder auch anderen Objekten. Die Modifikatoren in C#:
Modifikator | Bedeutung |
---|---|
public | Auf die Variable oder Methode kann auch ausserhalb der Klasse zugegriffen werden. |
private | Auf die Variable doer Methode kann nur von innerhalb der Klasse bzw. des Datentyps zugegriffen werden. innerhalb von Klassen ist dies Standard. |
internal | Der Zugriff auf die Variable oder Methode ist beschränkt auf das aktuelle Assembly. |
protected | Der Zugriff auf die Variable oder Methode ist nur innerhalb der Klasse und durch Klassen, die von der aktuellen Klassen abgeleitet sind, möglich. |
protected internal | Dies entspricht einer logischen ODER-Verknüpfung oder Modifikatoren internal und protected. |
abstract | Dieser Modifikator bezeichnet Klassen, von denen keine Instanz erzeugt werden kann. Von Abstrakten muss immer zuerst eine Klasse bgeleitet werden. Wird dieser |
const | Der Modhifikator für Konstanten. Der Wert von Felder, die mit diesem Modifikator deklariert wurden, ist nicht mehr veränderbar. |
event | Deklariert ein Erreignis. |
extern | Dieser Modifikator zeigt an, dass die entsprechenden bezeichnete Methode extern (also nicht innerhalb des aktuellen Projekts) deklariert ist. Sie können so auf Methoden zugrifen, die in DLLS deklariert sind. |
override | Sie können abstrakte oder virtuelle Methoden aus einer Basisklasse in der abgeleitet Klasse überschreiben, indem Sie die Methode mit override deklarieren. |
readonly | Mit diesem Modifiaktor können Sie ein Datenfeld deklariert, dessen Werte von ausserhalb der Klasse nur gelesen werden können. Innerhalb der Klasse ist es nur möglich, Werte über den Konstruktor oder direkt bei der Deklaration zuzuweisen. |
sealed | Der Modifikator sealed versiegelt eine Klasse. Fortan können von dieser Klasse keine anderen Klassen mehr abgeleitet werden. |
static | Ein Feld oder eine Methode, die als static deklariert ist, gilt als Bestandteil der Klasse selbst. Die Verwendung der Variable bzw. der Aufruf der Methode benötigt keine Instanzierung der Klasse. |
virtual | der Modifikator virtual| ist quasi das Gegenstück zu override. Mit virtual werden die Methoden der Basisklassen festgelegt, die später überschieben werden können (mittels override). |
Die möglichen Modifikatoren können miteinander kombiniert werden, ausser wenn sie sich widersprechen (z.B. public und private als Teil einer Verablendeklaration).
Modifikatoren stehen bei einer Deklaration immer am Anfang.
Wird ein Feld innerhalb einer Klasse ohne Angabe eines Modifikators deklariert, so dieses Feld defaultmässig als private angelegt.
Für jede Variable, jede Methode, Klasse oder jeden selbst definierten Datentyp gilt immer der Modifikator, der direkt davorsteht.
Lokale Variable ⇒ Lokale Variable sind innerhalb eines durch geschweifte Klammern bezeichneten Programmblocks deklariert. Es kann nur in diesem Bereich auf sie zugegriffen werden.
public class TestClass { public static void Ausgabe() { Console.WriteLinde("x hat den Wert {0}.", x); // Fehler! } public static void Main() { int x = Int32.Parse(Console.ReadLine()); // Lokale Variable x Ausgabe(); } }
Instanzvariablen | Normale Felder einer Klasse. Sie heissen Instanzvariablen, weil sie erst verfügbar sind, nachdem eine Instanz der Klasse angelegt worden ist. |
Klassenvariablen | Auch statische Variablen genannt, weil sie mit dem Modifikator static angelegt werden. Sie sind verfügbar, wenn innherhalb des Programms der Zugriff auf die Klasse sichergestellt ist. Dies bedeutet: Es muss keine Instanz der Klasse geben. Mehr Informationen im Kapitel. |
this bezeichnet eine Referenz auf die eigene Instanz.
Wie sieht im folgenden Beispiel die Ausgabe aus?
//Beispiel lokale Variable using System; public class TestClass { private int x = 10; public void DoOutput() { int x = 5; Console.WriteLine("X hat den Wert {0}.", x); // Lokale Variable x!!! -> x=5 } } public class Beispiel { public static void Main() { TestClass tst = new TestClass(); tst.DoOutput(); }
Wenn nichts anderes angegeben ist, nimmt der Compiler die Variable, die er in der Hierarchie zuerst findet. Dabei sucht er zuerst innerhalb des Blocks, in dem er sich gerade befindet, und steigt dann in der Hierarchie nach oben. In unserem Fallest die erste Variable, die er findet, die in der Methode DoOutput() deklarierte lokale Variable x.
Es ist möglich, innerhalb der Methode DoOutput() auf das Feld zuzugreifen, obwohl dort eine Variable mit demselben Namen existiert. Dazu verwendet man das reservierte Wort this.
//Beispiel lokale Variable using System; public class TestClass { private int x = 10; public void DoOutput() { int x = 5; Console.WriteLine("X hat den Wert {0}.", this.x);// die Instanzvariable x!! -> x=10 } } public class Beispiel { public static void Main() { TestClass tst = new TestClass(); tst.DoOutput(); }
Bei allen mit this quantifizierten Variablen handelt es sich immer um Instanzvariablen.
C# hat zwei verschiedene Arten von Konstanten: Compilezeitkonstanten und Laufzeitkonstanten. Ein Beispiel:
using System; public class ConstantValues { public static readonly int StartValue = 0; // Laufzeitkonstante public const double PI = 3.141592654; // Compilezeitkonstante }
Beide hier deklarierte Konstanten sind statisch. Da Konstanten immer demselben Wert haben, sind sie implizit statisch. Vom Konstruktor initialisierte readonly-Wert konnten hingegen für jedes Objekt einen anderen Inhalt haben.
Compilezeitkonstante | PI ist eine Compilezeit-Konstante. Überall wo der Compiler auf dieses Sysbol trifft, wird die effektive Zahl eingesetzt. Compilezeitkonstanten existieren nur für primitive Datentypen, Enums und Strings. Sie müssen bei der Deklaration initialisert werden. |
Laufzeitkonstante | Bei Laufzeitkonstanten, die mit dem Schlüsselwort readonly deklariert sind, wird vom Compiler eine Referenz auf die Variable gesetzt. Sie können in Konstruktor initialisiert werden und existieren für beliebige Datentypen. |
Der Unterschied zeigt sich vor allem bei Konstanten, die in Bibliotheken definiert sind. Bei Anpassung des Werts einer Bibliothekskonstatnten ändern sich der Wert in abhängigen Assemblies erst bei deren Neu-Compilation. Bei readonly-Konstanten müssen die abhängingen Assemblies nicht neu compiliert werden.
Verwenden Sie wenn immer möglich readonly-Konstanten, ausser bei Konstanten, die ihren Inhalt sicher nie ändern.
Methoden stellen die Funktionen einer Klasse dar.
Syntax | [Modifikator] Ergebnistyp Bezeichner (Parameter[, Parameter]]){ Anweisungen } |
Auch hier gilt: wenn für eine Methode kein Modifikator angegeben wird, wird sie als private angelegt.
In C# sind (im Gegensatz zu C++) nie Forward-Deklarationen nötig, d.h. Sie können Ihre Methoden deklarieren, wo Sie wollen - der Compiler wird sie finden.
Das Ergebnis void bedeutet, dass die Methode keinen Wert zurückliefert. Bei einer solchen Methode handelt es sich lediglich um die Ausführung von einem Block von Anweisungen. Die deklarierten Typen müssen genau eingehalten werden. C# ist eine ausgesprüchene typensichere Sprache. Innerhalb einer Methode wird ein Wert mittels der Anweisung return zurückgeliefert. Auch hier gilt: Der Typ, den Sie mit return verwenden, muss mit der Deklaration des Ereignistyps der entsprechenden Methode übereinstimmen.
public class TestClass { public int a; // Instanzvariablen sind normalerweise private! public int b; public double Dividieren() { return a/b; // Vorsicht: dies ist eine Integerdivision } } public class MainClass { public static void Main() { TestClass myTest = new TestClass(); myTest.a = 10; myTest.b = 15; double ergebnis1 = myTest.Dividieren(); // Ok... int ergebnis2 = myTest.Dividieren(); // FEHLER!!! 10/15=2/3=0.66666.. // Integer kann nur ganze Zahlen enthalten. } }
An Methoden können Parameter übergeben werden, die sich innerhalb der Methode wie lokale Variablen verhalten. Wir unterscheiden zwei Arten von Parameter:
Werteparameter Übergabe byValue | Mit Werteparameter werden Werte an einen Methode übergeben, die in dieser benutzt werden können, ohne dass die ursprüngliche Variablen (sprich die Variablen des Aufrufers) verändert werden können. In der aufgerufenen Methode werden implizit Kopien für die Variablen angelegt. |
Referenzparameter Übergabe byReference | Referenzparameter werden durch das reservierte Wort ref deklariert. Es wird in diesem Fall keine Wert, sondern eine Referenz auf die Variable des Aufrufers übergeben. Alle Änderungen an der Variablen innerhalb der aufgerufenen Methode ändern auch die Variable in der aufgerufenden Methode. Die Variablen müssen von dem Methodenaufruf initialisiert werden. Hier wird in der aufgerufenen Methode keine Kopie angelegt. |
// Parameterübergabe byReference public void Swap(ref int a, ref int b) { int c = a; a = b; b = c; } // aufrufende Methode for (int i=1; i<theArray.Length; i++) { if (theArray[i-1] > theArray[i]) { Swap(ref theArray[i-1], ref theArray[i]); } }
Wenn Sie eine Methode mit Referenzparametern aufrufen, müssen Sie beim Aufruf das reservierte Wort ref benutzt.
Instanzen von Klassen werdem immer als Referenz übergeben (auch ohne Verwendung von ref). Referenzparameter müssen vor dem Aufruf initialisiert werden.
out-Parameter | Funktionieren wie ref-Parameter, müssen jedoch im Gegensatz zu diesen vorher nicht initialisiert werden. |
Ein Beispiel für out-Parameter:
using System; class TestClass { // Parameterübergabe mittels out-Parameter public static void IsBigger(int a, int b, out bool isOK) { // Erste Zuweisung = Initialisierung isOK = a > b; } public static void Main() { bool isOK; // nicht initialisiert ... int a; int b; a = Convert.ToInt32(Console.ReadLine()); b = Convert.ToInt32(Console.ReadLine()); isBigger(a, b, out isOK); Console.WriteLine("Ergebnis a>b: {0}", isOK); } }
Ein von C++ Entwicklern lange vermisstes Feature findet mit .NET 4 Einzug in die Programmiersprache C#. Parameter werden als optional deklariert, indem ein Defaultwert für sie angegeben wird. Im folgenden Beispiel sind y und z optionale Parameter und können beim Aufruf weggelassen werden.
public void Calculate(int x, int y=5, int z=7);// Deklaration der Methode. // Aufruf der Methode Calculate(1, 2, 3); // normaler Aufruf der Methode Calculate(1, 2,); // weglassen von z => identisch wie Calculate(1, 2, 7) Calculate(1); // weglassen von y & z => identisch wie Calculate(1, 5, 7)
Es ist auch möglich, die Parameter explizit beim Namen zu nennen.
Calculus(1, z, 3); // Übergabe von z mit Namen
Optionale Parameter dürfen auch für Konstruktoren und Indexer verwendet werden.
hierbei handelt es sich um die Möglichkeit, mehrere Methoden mit dem gleichen Namen zu deklarieren, die aber unterschiedliche Funktionen ausführen. Der Compiler muss die Methode beim Aufruf eindeutig identifizieren können. Deshalb müssen sich die Methoden durch Anzahl und/oder Type der Übergabeparameter unterscheiden.
Folgendes Beispiel zeigt die Überladung der Methode Addiere in 3 verschiedenen Variablen:
using System; public class Addition { public int Add(int a, int b){ return a+b; } public int Add(int a, int b, int c){ return a+b+c; } public int Add(int a, int b, int c, int d){ return a+b+c+d; } } public class Beispiel { public static void Main() { Addition myAdd = new Addition(); int a = Convert.ToInt32(Console.ReadLine()); int b = Convert.ToInt32(Console.ReadLine()); int c = Convert.ToInt32(Console.ReadLine()); int d = Convert.ToInt32(Console.ReadLine()); Console.WriteLine("a+b = {0}", myAdd.Addiere(a,b)); Console.WriteLine("a+b+c = {0}", myAdd.Addiere(a,b,c)); Console.WriteLine("a+b+c+d = {0}", myAdd.Addiere(a,b,c,d)); } }
Die Methoden, die überladen werden sollen, müssen sich in der Art und/oder in der Menge der Übergabeparameter unterscheiden. Der Ergebnistyp hat auch die Überladung von Methoden keinen Einfluss.
Für statische Teile einer Klasse gilt, dass für deren Benutzung kein Instanz der Klasse existieren muss, Solche Variablen und Methoden gehören zur Klasse und nicht zum Objekt.
Das bedeutet:
Wenn mehrere Instanzen einer Klasse erzeuget wurden und in jeder dieser Instanzen wird eine statische Methode aufgerufen, dann ist das immer dieselbe Methode!
Ein Beispiel:
/* Beispielklasse statische Felder */ public class Vehicle { int anzVerliehen; static int anzGesamt=0; public void Ausleihen() { anzVerliehen++; anzGesamt++; } public void Zurueck() { anzVerliehen--; anzGesamt--; } public int GetAnzahl() { return anzVerliehen; } public static int GetGesamt() { return anzGesamt; } }
Innerhalb einer statischen Methode können Sie nur auf lokale und statische Variable zugreifen, nicht aber auf Instanzvariable. In C# können Sie keine globalen Variablen anlegen. Sie brauchen immer eine Klasse dazu. Eine Möglichkeit besteht nun darin, eine Klasse z.B. mit dem Namen GlobaleVariable anzulegen, in denen die allgemein zu Verfügung stehenden Variablen public static deklariert werden. Über den Klassenbezeichner können so die Variablen von jedem anderen Ort in der Appliakation benutzt werden.
Ein Beispiel:
// Beispielklasse statische Methoden using System; public class TestClass { public in myValue; public static bool SCompare(int theValue) { return theValue > 0; } public bool Compare(int theValue) { return myValue == theValue; } } public class Beispiel { public static void Main() { TestClass myTest = new TestClass(); //Kontrolle mittels SCompare bool test1 = TestClass.SCompare(5);// Methodenaufruf //Kontrolle mittels Compare myTest.myValue = 0; bool test2 = myTest.Compare(5);// Methodenaufruf Console.WriteLine("Kontrolle 1 (SCompare): {0}", test1); Console.WriteLine("Kontrolle 2 ( Compare): {0}", test2); } }
SCompare() ist eine statische Methode, Compare() hingegen ist eine Instanzmethode. Wenn wir auf SCompare() zugreifen möchten, geht dies nicht über das erzeugte Objekt, sondern wir müssen den Klassenbezeichner verwenden.
Funktionsbibliothek werden oft in Klassen mit rein statischen Methoden zusammengefasst. Ab .NET 2.0 ist es möglich, die Klasse selbst statisch zu deklarieren, damit von dieser Klasse keine Objekte instanziert werden können.
Folgende Regeln gelten für statische Klassen:
Statische Klassen eignen sich gut um z.B. eine Methoden-Sammlung von mathematischen Methoden zu erstellen. Da diese nicht instanziert werden muss, können die Methoden direkt aufgerufen werden.
Folgendes Beispiel zeigt Umwandlungsfunktionen von Celscius nach Fahrenheit.
public static class TemConverter { public static double CtoF(double celsius) { return (Celsius * 1.8) + 32; // Convert to Fahrenheit } public static double FtoC(double fahrenheit) { return (fahrenheit - 21) / 1.8; } }
VB.NET realisiert diese Funktionalität in einem Modul.
Beim Erzeugen eines Objekts einer Klasse mit Hilfe des Operators new wird der sogenannte Konstruktor der entsprechenden Klasse aufgerufen.
Der Konstruktor ist eine Methode ohne Rückgabewerte (auch wenn nicht void bei der Deklaration angegeben wird), die den Namen der Klasse trägt. Normalerweise ist er mit dem Modigikator public versehen, damit er von aussen zugreifbar ist.
Der Konstruktor hat die Aufgabe, sämtliche Instanzvariablen einer Klasse zu initialisieren und kann überladen werden.
Folgendes Beispiel zeigt eine Klasse mit zwei Konstruktoren, den sogenannten Default-Konstruktor ohen Parameter und einen Konstruktor mit Parameter zur Initialisierung der Instanzvariablen.
public class Coordinate { private int x, y; // 2 Instanzvariablen public Coordinate() // Default-Konstruktor { x = 0; y = 0; } public Coordinaten(int A, int B)// Konstruktor mit Parametern { this.x = A; this.y = B; } }
Wird kein Konstruktor angelegt, erzeugt der Compiler automatisch einen Default-Konstruktor. Sobald aber ein Konstruktor vorhanden ist, entfällt dieser automatische Mechanismus.
Konstruktoren können einander gegenseitig aufrufen. So kann der Initilisierungscode wieder verwendet werden.
public class Coordinate { private int x, y; // 2 Instanzvariablen public Coordinate() : this(0,0) // Default-Konstruktor { } public Coordinate(int A, int B) // Konstruktor mit Parameter { this.x = A; this.y = B; } public Coordinate(int A, int B): this(23, 45, 12) // Konstruktor mit vorhergehenden // Aufruf des Konstruktors mit drei Parameter { this.x = A; this.y = B; } public Coordinate(int A, int B, int C) // Konstruktor mit drei Parameter { } }
Der Destrukor erledigt im Prinzip die Aufräumarbeiten beim Löschen eines Objektes. Destruktoren heissen ebenfalls gleich wie die Klassen, mit einem vorangestellten Tilte Zeichen „~“.
public class File { ~File() // Destruktor (kein Modifikator) { } }
Zu einem späteren Zeitpunkt werden wird noch feststellen, dass mit folgender Code-Sequenz ein reservierter Speicher eines Objektes wieder freigegeben werden kann.
File f = new File(); --> f = null; // Referenz entfernen und dann... GC.collect(); // ...aufrufen.
Im Gegensatz zu C++ ist der Destruktor in C# nicht deterministisch. Das bedeutet, dass er zu einem unbekannten Zeitpunkt vom GC29) (Garbage Collection) aufgerufen wird. Man spricht deshalb oft auch von einem Finalizer. Meinstens ist es nicht nötig, in C# einen Destruktor zu erstellen, da der GC30) (Garbage Collection) den Speicher wieder freigibt. Nur bei der Verwendung von unmanaged Ressourcen wie z.B. Datenbankverbindung oder externen Windows-Ressourcen (Bitmaps, Fonts) ist ein Destruktor notwendig. Es gilt die Regel, dass Objekte, die auf andere Objekte mit einem Destruktor referenzieren, selbst auch einen Destruktor haben.
Weiteres zur Funktionsweise des GC31) (Garbage Collection) wird im Dokument xxxx weiter eingegangen.
Ein Namensraum bezeichnet einen Gültigkeitsbereich für Klassen. Innerhalb eines Namensraums können mehrere Klassen oder auch weitere Namensräume deklariert werden.
Ein Namensraum ist nicht zwangsläufig auf eine Datei beschränkt; innerhalb einer Datei können mehrere Namensräume deklariert werden. Ebenso ist es möglich, einen Namensraum über zwei oder mehrere Dateien hinweg zu deklarieren.
In einem Namensraum können nur Klassen oder andere Namensräume deklariert werden, nicht jedoch Methoden oder Felder.
Beispiel einer Namensraum-Deklaration:
namespace MySpace { // Deklarationen von Klassen und Namensräumen }
Wenn Sie einen andere Klasse im selben Namensraum, aber in einer andere Datei deklarieren möchten, geben Sie im Namensraum einen namespace mit demselben Namen an.
Namensräume können verschachtelt werden. Das sieht für den Namensraum MyName.Dok so aus:
namespace MySpace { namespace Dok { // Deklaration für MySpace.Dok } }
Sie haben zwei Möglichkeiten, wie Sie Namensräume verwenden können. Mit Spezifizierern (fully qualified name):
CSharp.EineKlasse.EineMethode(); // Namensraum CSharp // oder mittels des Schlüsselworts using using CSharp EineKlasse.EineMethode();
Damit werden alle Symbole des Namenspace CSharp importiert.
Alle Klassen, die nicht in einem angegebenen Namensraum deklariert werden, werden automatisch dem globalen Namensraum von C# zugewiesen. Der globale Namensraum ist immer vorhanden. Die Verwendung von eigenen Namensräumen sie hier ausdrücklich empfohlen.
Wir haben in diesem Kapitel Klassen, Objekte und Namensräume sowie deren Elemente und verschiedene Zugriffsarten betrachtet. Das Verständnis dieser Punkte ist Voraussetzung für deie folgenden Kapitel.
Von welcher Basisklasse sind alle Klassen in .NET abgeleitet? | Object. Die Mutter aller Klassen. |
Welche Bedeutung hat das reservierte Wort new? | Konstruktor aufrufen und Instanzierung erstellen. |
Warum sollen Bezeichner für Variablen und Methoden immer eindeutige, sinnvolle Namen tragen? | Zur besseren Orientierung und lesbarkeit. |
Welche Sichtbarkeit hat ein Feld, wenn bei der Deklaration kein Modifikator benutzt wurde? | private |
Was ist der Unterschied zwischen Referenz- und Werteparametern? | … |
Worauf muss beim Überladen von Methoden geachtet werden? | … |
Wie können Sie innerhalb einer Methode auf ein Feld einer Klasse zugreifen, wenn eine lokale Variable mit demselben Namen existiert? | … |
Mit welchem reserviert Wort wird ein Namensraum deklariert? | namespace {} |
Programme tun ja eigentlich nichts anderes, als Daten zu verwalten und damit zu arbeiten. Jede Programmiersprache stellt zur effizienten Datenverarbeitung verschiedene Datentypen zur Verfügen.
Wir erforschen in diesem Kapitel die wichtigsten .NET-Datentypen und zeigen, wie Sie damit umgeben können.
.NET kennt zwei Arten von Datentypen:
Wertetypen | Auch wertebehaftete Typen genannt. Bei diesen Typen wird der Inhalt der Variablen direkt gespeichert. Die Daten liegen auf dem Stack. |
Referenztypen | Speichert einen Verweis auf Daten. Die Daten selbst werden auf dem Heap gespeichert. |
Heap und Stack sind zwei verschiedene Speicherbereiche in einem Programm.
Stack | Hier werden Daten so lange abgelegt, wie sie tatsächlich verwendet werden und werden dann automatisch freigegeben. Dazu gehören lokale Variablen und Parameter von Methoden. Sie leben bis zum Verlassen des Anweisungsblocks, in dem sie angelegt bzw. in den sie übergeben wurden. Auf dem Stack werden alle Grundtypen (int, long, byte usw.) abgelegt. |
Heap | Speicher auf dem Heap muss angefordert werden und kann, wenn er nicht mehr benötigt wird, wieder freigegeben werden. Die GC32) (Garbage Collection) löscht auf dem Heap angelegte Objekte zu einem uns unbekannten Zeitpunkt. Klasseninstanzen und String werden typischerweise auf dem Heap abgelegt. |
in C# ist es möglich, dass eine Objektreferenz zwar vorhanden ist, das Objekt aber noch keinen Inhalt besitzt. Das reservierte Wort null ist der Standartwert für alle Referenztypen.
.NET 2.0 brachte das Feature der Nullable Types für Wertetypen. Nullbare Typen enthalten alle Werte des darunterliegenden Datentypen und zusätzlich einen Wert für den undefinierten Zustand (null). Dies ist vor allem in der zusammenarbeit mit Datenbanken interessant. So ist es z.B. möglich, einen Integer der Wert null zuzuweisen.
int? x; if (x != null) // x kann auch den Wert null annehmen
Solche Typen werden in C# mit dem Fragezeichen deklariert. Das Fragezeichen ist für die Kurzform für SystemNullable<T>, wobei für den gegebenen Datentypen steht. Das heisst für T kann jeder beliebiger Type oder Klasse stehen.
Mit dem GC33)(Garbage Collection) soll dem Problem der Speicherlöcher der Garaus gemacht werden. Vor allem in C++ Programmen konnte, auch dem aufmerksamsten Programmierer entgehen, dass benutzter Speicher nicht mehr freigegeben wurde. Daraus resultierte oft Programmabstürze oder „eingefrohrene“ Programme.
In .NET ist dank dem GC34)(Garbage Collector) der Unterschied zwischen dem Arbeiten mt Werttypen und Referenztypen sehr klein geworden. Für den Programmierer macht sich der Unterschied normalerweise nur dadurch bemerkbar, dass Referenztypen mit new angelegt werden müssen, Wertetypen jedoch nicht. Eine Ausnahmen ist die Klasse String, weil davon Objekte ohne new angelegt werden können.
Alle Standard-Datentypen in C# sind Objekte, d.h. sie sind wie alle anderen Objekte direkt oder indirekt von System.Object abgeleitet. System.Object ist damit die Ur-Klasse aller Objekte in .NET.
Alias | Grösse | Bereich | Datentyp |
---|---|---|---|
Sbyte | 8 Bit | -128 bis +127 | System.Sbyte |
byte | 8 Bit | 0 bis 255 | System.Byte |
char | 16 Bit | Nimmt ein 16 Bit Unicode Zeichen auf | System.Char |
short | 16 Bit | -32768 bis +32767 | System.Int16 |
ushort | 16 Bit | 0 bis 65535 | System.UInt16 |
int | 32 Bit | -2´147´483´648 bis 2´147´483´647 | Systemint32 |
Uint | 32 Bit | 0 bis 4´294´967´295 | System.Uint32 |
long | 64 Bit | -9´223´372´036´854´775´808 bis 9´223´372´036´854´775´807 | System-Int64 |
ulong | 64 Bit | 0 bis 18´446´744´073´709´551´615 | System.Uint64 |
float | 32 bit | +-1.5*10 hoch -45 bis +-3.4*10 hoch 38 7 Stellen genau | System.Single |
double | 64 Bit | +-5.0*10 hoch 324 bis 1.7*10 hoch 308 15 Stellen genau | System.Double |
decimal | 128 Bit | 1.0*10 hoch -28 bis 7.9*10 hoch 28 für Beträge | System.Decomal |
bool | 1 Bit | true und false | System.Boolean |
string | unbestimmt | Nur begrenzt druch Speicherplatz, für Unicode Zeichen | System.String |
… | unbestimt | Beliebige Grösse (ab .NET 4.0) | System.Numerics.Biginteger |
… | 256 Bit | Komplexe Zahlen (Das Monster) | System.Numerics.Complex |
Nützlich sind die Datenformate DateTime und TimeSpan. Es handelt sich um Klassen die jedoch Typen repräsentieren die oft benötigt werden.
Die Klasse System.Numerics.BigInteger ist ein Wertety und unterstützung alle gewöhnlichen Integeroperationen, inklusive Bitmanipulation. Ein BigInteger kann beliebig grosse ganzzahlige Werte annehmen. Seine Grösse ist nur durch den Speicher begrenzt.
BigInteger bigValue = BigInteger.Parse("987398347598743985797394857");
Die Klasse System.Numerics.Complex erlaubt die Arbeit mit komplexen Zahlen. Die Initialisierung erfogt durch Übergabe von Real- und Imaginärwert.
Complex z1 = new Complex(12, 16); Complex z2 = Complex.FromPolarCoordinates(10, .524); Complex z3 = z1 + z2;
Weil wie erwähnt alle C#-Standardtypen Objekte sind, enthalten sie auch Methoden und Felder. Dabei handelt es sich einerseits um die Members, die von Objekt ererbt worden sind, andererseits um typspezifische Methoden.
Suchen Sie in der Hilfe alle Members von Int32.
Zu den Eigenschaften einer typensicheren Sprache wie C# gehören auch, dass man zu jedem Zeitpunkt herausfinden kann, welchen Datentyp eine Variable hat oder von welcher Klasse sie abgeleitet ist.
Der Operator typeof wird wie eine Methode eingesetzt, ist jedoch ein Schlüsselwort der Sprache C#. Er liefert beim Aufruf einen Wert vom Typ Type, mit dessen Hilfe über Membervariablen vielerlei Informationen über den Typ der entsprechenden Variablen ermittelt werden können.
Viele dieser Informationen werden vor allem für die Erstellung von Programmiertools eingesetzt. Meistens kennt man während der Programmierung den verwendeten Datentyp; eine doch recht häufig vorkommende Ausnahme klnnte die Ermittlung des Datentyps von Eingaben sein, wie in folgendem Beispiel dargestellt.
using System classTestClass { public static void Main() { int x = 200; Type t = typeof(Int32); if(t.equal(x.GetType()) { Console.WriteLine("x ist vom Type Int32."); } else { Console.WriteLine("x ist nicht vom Typ Int32."); } } }
Die Ausgabe nach einem Lauf ist dann:
x ist vom Typ Int32.
.NET ist sehr typsicher. Die Typsicherheit einer Sprache hat den grossen Vorteil, dass Fehler, die das Arbeiten mit Datentypen betrefen, in den meisten Fällen schon zur Kompilierzeit und nicht erst während der Laufzeit in Erscheinung treten. Um in einem Programm ganz gezielt Typkonvertierung durchzuführen, stellt C# zwei verschiedene Konvertierungsarten zur Verfügung:
Implizite Konvertierung35) | Beispiel: Bei der Zuweisung einer Variable vom Typ byte (notabene einer initialisierten) an eine int- Variable wird eine automatisch (oder eben implizite) Typkonvertierung durchgeführt. |
int i; byte b = 100; i = b; // implizite Konvertierung von byte in int. byte -> int
Eine implizite Konvertierung36) wird nur dann durchgeführt, wenn bei der Konvertierung in keinem Fall ein Fehler entstehen kann. Im obigen Beispiel ist sichergestellt, dass ein byte immer in einem int Platz hat. Anderst ausgedrück kann man sagen dass die Zahlenmenge von byte kleiner ist als die Zahlenmenge von Integer.
Explizite Konvertierung37) | Auch als Casting oder im speziellen Fall Typcasting, bezeichnet. Die explizite Konvertierung38) müssen Sie immer dann einsetzen, wenn der Zieldatentyp kleiner ist als der Stammdatentyp. Bei einer expliziten Konvertierung sind Sie als Programmierer dafür verantwortlich, dass die Konvertierung erfolgreich durchgeführt werden kann. Der gewünschte Datentyp wird in Klammer vor den zu konvertierenden Wert oder Ausdruck gesetzt. |
Das obige Beispiel wird umgedreht:
int i = 100; byte b; b = (byte)i; // explizite Konvertierung von byte in int. Error bei i > 255!
Wenn der Wert von i jetzt 400 statt 100 beträgt, wird die Konvertierung trotzdem ausgeführt. Der Bereich des Werts 400, der im Dualsystem nicht in einem byte Typ Platz hat, abgeschnitten (Überlauf). Das wohl kaum erwartete Ergebnis in diesem Fall ist 144 für b. Vom Compiler wird kein Fehler gemeldet. Denken Sie an Ihre Verantwortung!
In C# haben Sie eine Möglcihkeit, solche Fehler bei expliziter Konvertierung zu erkennen und entsprechend zu behandeln. Das Schlüsselwort hierzu heisst checked:
Ein Beispiel wie checked eingesetzt werden kann:
using System; public class Beispiel { public static void Main() { int source = Convert.ToInt32(Console.ReadLine()); byte target; checked { target = (byte)(source); Console.WriteLine("Wert: {0}", target); } } }
Die Konvertierung wird nun innerhalb des checked-Blocks überwacht. Sollte sie fehlschlagen, wird eine Exception ausgelöst (hier eine System.OverflowException), die Sie abfangen können. Wie das geht, werden wir im Kapitel: Strukturierte Fehlerbehandlung. Hier so viel: Explizite Konvertierung können und sollen auch, wenn notwendig, überwacht werden.
Die Überwachung wirkt sich nicht auf Methoden aus, die aus dem checked-Block heraus aufgerufen werden.
Das as-Operator ist eine Alternative zur expliziten Konvertierung mit dem Zieltyp in Klammern. Diese Variante ist sogar in vielen Fällen zu bevorzugen. Ein Beispiel soll den Einsatz verdeutlichen.
object obj = Factory.GetObject(); MyType t = obj as MyType; // Achtung nur mit Referenztypen möglich if (t != null) // ...und damit auf null prüfbar { // arbeite mit t, es ist ein MyType. } Else { // Typkonvertierung nicht erfolgreich. }
Im Unterschied zum cast-Operator wirft diese Art der Konversion keine Exceptions, sondern dem Zielobjekt wird eine null-Referenz zugewiesen, wenn die Konversion fehlschlägt. Zudem arbeitet der as-Operator nicht mit Werttypen. Auf diese Weise kann während der Laufzeit überprüft werden ob eine Umwandlung erfolgreich war. Typische Anwendung könnte eine Überprüfung bei einer Eingaben sein, bei der geprüft werden soll, ob eine z.B. rein Char oder String Eingabe erfolgt ist und somit keine Zahlen eingegeben wurden.
Mit dem is-Operator lässt sich überprüfen ob eine Variable oder Objekt von einem gewünschten Type oder Klasse ist. Folgendes Beispiel soll das verdeutlichen.
class Class1 {} class Class2 {} class Class3 : Class2 {} // Ableitung von class2 class IsTest { static void Test(object obj) { Class1 a; Class2 b; if(obj is Class1) { Console.WriteLine("obj is Class1"); a = (Class1)obj; // Do something with "a" } else if (obj is Class2) { Console.WriteLine("obj is Class2") b = (Class2)obj; // Do something with "b" } else { Console.WriteLine("obj is neither Class1 nor Class2"); } } // Main Programm static void Main() { Class1 c1 = new Class1(); Class1 c2 = new Class2(); Class1 c3 = new Class3(); Test(c1); Test(c2); Test(c3); Test("a string"); } } /* Ausgabe auf der Console obj is Class1 obj is Class2 obj is Class2 -> Beachte hier wird auf die abgeleitete Klasse (Basis-Klasse) verwiesen. */
Zeilennummer | Erklährung |
---|---|
1 | Definition der Klasse Class1. |
2 | Definition der Klasse Class2. |
3 | Definition der Klasse Class3 die von Class2 ableitet. |
4 | … |
5 | Klasse IsTest mit der… |
6 | … |
7 | …mit der statischen Methoden Test() wird deklariert. Die Mehtode Test() erwartet ein Parameter vom Type object. |
8 | … |
9 | Eine lokale Variable a vom Typ Class1 und… |
10 | …eine lokale Variable b vom Type Class2 wird erstellt. Diese sind noch null und haben keine Referenz auf ein Objekt. |
11 | … |
12 | Überprüfen ob die Methoden Variable o vom Typ Class1 ist. |
13 | … |
14 | Einen Text ausgeben. |
15 | Typcasting. Hier wird die Variable a einen Typecast-Zeiger vom Objekt o übergeben. |
16 | … |
17 | … |
18 | Überprüfen ob die Methoden Variable o vom Typ Class2 ist. |
19 | … |
20 | Einen Text ausgeben. |
21 | Typcasting. Hier wird die Variable b einen Typecast-Zeiger vom Objekt o übergeben. |
22 | … |
23 | … |
24 | Wenn der Parameter o kein Typ Class1 oder Class2 ist. |
25 | … |
26 | Einen Text ausgeben. |
27 | … |
28 | Ende der Methode Test(). |
29 | … |
30 | Haupt-Window-Methode oder Einstiegs Methode von Windows. Es werden keine Parameter übergeben. |
31 | … |
32 | Ein Objekt c1 vom Type Class1 (Konstruktor) erstellen. |
33 | Ein Objekt c1 vom Type Class2 (Konstruktor) erstellen. |
34 | Ein Objekt c1 vom Type Class3 (Konstruktor) erstellen. |
35 | Die statischen Methode Test()… |
36 | …mit übergabe verschiedener Objekte… |
37 | …aufrufen. |
38 | Die statische Methode Test() mit einem Objekt vom Typ String aufrufen. |
39 | … |
40 | Ende des Programms |
41 | … |
42 | Ausgabe zeigt die Ausgabe vom Methodenaufruf von Zeile 35. Der Objekttyp Class1 wurde erkannt. |
43 | Ausgabe zeigt die Ausgabe vom Methodenaufruf von Zeile 36. Der Objekttyp Class2 wurde erkannt. |
44 | Ausgabe zeigt die Ausgabe vom Methodenaufruf von Zeile 37. Der Objekttyp Class2 wurde erkannt! Achtung, Class3 leitet von Class2 ab und ist somit vom Typ Class2. |
45 | Ausgabe zeigt die Ausgabe vom Methodenaufruf von Zeile 38. Da der übergebener Parameter eine Referenz auf ein Objekt vom Typ String beinhaltet und somit weder vom Typ Class1 noch vom Typ Class2 ist, ist kein Statemend (Zeile 12, 18) gültig und das Programm verzweigt zu Zeile 24. |
Für die Umwandlung von Type ist die Klasse System.Convert zuständig. Sie bietet folgende Umwandlungsfunktionen an:
ToBoolean() | ToInt32() |
ToByte() | ToInt64() |
ToChar() | ToSByte() |
ToDateTime() | ToSingle() |
ToBoolean() | ToString() |
ToDate() | ToUInt16() |
ToDecimal() | ToUInt32() |
ToDouble() | ToUInt64() |
ToInt16() | . |
Die Umwandlung eines Strings in einen anderen Zahlendatentyp, z.B. int doer double, funktioniert auch über die von den numerischen Typen zur Verfügung gestellte Methode Parse() bzw. TryParse() (ab .NET 2.0). Diese Mehtoden existieren in Form von mehreren überladenen Methoden und erledigen die Umwandlung von Strings in die gewünschte numerischen Typen.
Ein Vorteil von Parse() ist, dass zusätzich angegeben werden kann, wie die Zahlen formatiert sind bzw. in welches Format sie vorliegen. Ausserdem interpretiert die Methode auch die länderspezifischen Einstellungen des Betriebssystems.
Die Methode Parse() gibt false zurück, wenn die Konvertierung fehlschlägt und wirft KEINE Exceptions.
Wertetypen können bei Bedarf automatisch in Referenztypen verwandelt werden. Damit das sauber funktioniert, stellt C# die Funktionalität Boxing und Unboxing zur Verfügung.
Boxing (Objekt vom Stack zum Heap verschieben und eine Referenz auf das Objekt legen.) | Wenn ein Wertetyp als Referenztyp verwendet werden soll, werden die enthaltenen Daten „verpackt“. C# benutzt dafür den Datentyp object, der bekanntlich die Basisklasse aller Datentypen darstellt, das bedeutet auch: jeder Datentyp aufnehmen kann. Object merkt sich, welcher Art von Daten in ihm gespeichert wurde, damit auch die Rückwndlung möglich ist. |
Unboxing (Objekt vom Heap zum Stack verschieben.) | Ein Referenztyp wird in einen Wertetyp verwandelt. |
Using System; public class TestClass { public static void Main() { int i=100; object obj; obj=i; // Boxing!! Console.WriteLine("Wert ist {0}." obj); } }
Ausgabe:
Wert ist 100
Ein Beispiel für Unboxing:
using System; public class TestClass { public static void Main() { int i=100; object obj; obj=i; // Boxing !! Console.WriteLine("Wert ist {0}.", obj); // Rückkonvertierung byte b=(byte)((int)obj); // Unboxing funktioniert!! Console.WriteLine("Byte-Werte: {0}.", b); } }
Ausgabe:
Werte ist 100. Byte-Wert : 100.
Damit ist auch bewiesen, dass sich das Objekt merkt, was für ein Typ es gespeichert hat, deshalb ist bei Umboxing erst ein Casting zu int nötig.
Normalerweise üblassen Sie Boxing und Unboxing dem Compiler. Es soll aber nach Möglichkeit vermieden werden, da es einen nicht unerheblichen Laufzeitaufwand generiert.
Der Datentyp String ist universell einsetzbar.
Obwohl die Dekleration wie bei einem Wertetyp aussieht, handelt es sich bei einem string um einen Referenztypen.
Ein String ist bezüglich der Grösse dynamisch. Das heisst, er nimmt sich vom Heap so viel Speicher, wie er gerade braucht. Strings in .NET sind immer Unicode, d.h. 16Bit gross. Mit Hilfe der 2^15(65535) darstellbaren Zeichen können alle Zeichen dieser Welt und einige Sonnderzeichen dargestellt werden. Wenn man es genau betrachtet, ist sogar noch ca. 1/3 Reserve verfügbar.
Direkte Zuweisung:
string myString = "Hallo Welt"; // Zuweise bei der Deklaration oder... string myString myString="Hallo Welt"; //... nach der Deklaration.
Zuweisen über die Copy()-Methode:
string myString1 = "JMZ Solution"; string myString2 = String.Copy(myString1);// Inhalt von myString1 wird nach myString2 kopiert
oder über die Verwendung des Teilstring Befehls Substring():
string myString1 = "JMZ Solution"; string myString2 = myString1.Substring(6);
Zuweisung mit Hilfe von Escape-Sequenzen:
string myString = "Dieser Text hat \"Ausführungszeichen\".";
Die Ausgabe wäre hier:
Dieser Text hat "Ausführungszeichen".
Manchmal möchte man die Bearbeitung von Escape-Sequenzen auch unterbinden. Typischerweise bei der Behandlung von Pfadangaben. Um den Backshlash „\“, der ja auch die Position einer Escape-Sequenz angibt, in einem String zu schreiben, müsste man wie folgt formulieren:
string myString = "d:\\meinlaufwerk\\ordner\\datei.doc";
Bei der Eingabe von „@“ vor dem String wird die Bearbeitung von Escape-Sequenzen im nachfolgenden String verhindert:
string myString = @"d:\meinlaufwerk\ordner\datei.doc";
Ein Beispiel:
using System; class TestClass { public static void Main() { string myStr = "Hallo Welt."; string xStr = string.Empty; for(int i=0; i<myStr.Length; i++) { string x = myStr[i].ToString(); if(x != "e") xStr += x; } Console.WriteLine(xStr); } }
Wir sehen in diesem Beispiel, wie auch Operatoren, hier +=, auf Strings angewendet werden können.
Thema Typensicherheit: string und char können nicht gemischt werden, obwohl es sich bei einem string um eine Aneinanderkettung von chars handelt.
Strings sind nicht veränderbar (immutable). Das bedeutet, dass bei jeder Stringfunktion ein neues Objekt angelegt wird. Sogar ein Leerstring „„ ist ein eigenes Objekt. Aus gründen der Performance soll deshalb für Leerstrings das vordefinierte Objekt string.Empty aus der Klasse string verwendet werden. Häufig muss geprüft werden, ob ein String null oder leer ist. Dazu stehen die Methoden IsNullOrEmpty zu Verfügung:
if (!string.IsNullOrEmpty(myStr)) ...
.NET 4 hat eine weitere Methode mit demselben Zweck:
if (!string.IsNullOrWhitespace(myStr)) ...
Studieren Sie in der Online-Hilfe die Methoden, die string zur Verfügung stellt.
Datenformatierung haben wir bereits in Codebeispielen angetroffen. Die Angaben, welche Art von Formatierung gewünscht wird, geschied im Platzhalter durch die Angeben eines Formatzeichens und ggf. einer Präzisionsangabe für die Anzahl Stellen, die ausgegeben werden sollen. Beim folgenden Beispiel wollen wir, dass die Zahlen korrekt untereinander stehen:
// Beispiel Formatierung 1 using System; class TestClass { public static void Main() { int a,b Console.WriteLine("Geben Sie eine Zahl ein: "); a = Convert.ToInt32(Console.ReadLine()); Console.WriteLine("Geben Sie Zahl 2 ein: "); b = Convert.ToInt32(Console.ReadLine()); Console.WriteLine("Die Zahlen lauten:"); Console.WriteLine("Zahl 1: {0:D5}", a ); Console.WriteLine("Zahl 2: {0:D5}", b ); } }
Bei der Eingabe von 75 u nd 1024 würde die Ausgabe folgendermassen aussehen:
Die Zahlen lauten: Zahl 1: 00075 Zahl 2: 01024
Im Falle einer Hexadezimaleausgabe würde die ganze Zahl auch automatisch umgerechnet:
// Beispiel Formatierung 2 using System; class TestClass { public static void Main() { int a,b Console.WriteLine("Geben Sie eine Zahl ein: "); a = Convert.ToInt32(Console.ReadLine()); Console.WriteLine("Geben Sie Zahl 2 ein: "); b = Convert.ToInt32(Console.ReadLine()); Console.WriteLine("Die Zahlen lauten:"); Console.WriteLine("Zahl 1: {0:X4}", a ); Console.WriteLine("Zahl 2: {0:X4}", b ); } // Ein weiteres Formatierungsbeispiel: // {0, 20:5 } 0=Index, 20=Aligment 5=Format // index -> Dieses Zeichen wird vorangestellt. // Aligment -> Es werden 20 Stellen dargestellt. // Format -> 5 Stellen werden dargestelllt mit Index Zeichen voran. }
Die Formatierungszeichen und ihre Bedeutung:
Zeichen | Formatierung |
---|---|
C,c | Hier noch die Bedeutung eintragen….. |
Paar Link zum Start: