© Martin Korneffel, Stuttgart 2003 +++ email: trac@n.zgs.de +++ web: www.s-line.de/homepages/trac

C#

Inhalt

  1. Einführung

    1. Ziel der Programmierung

      1. Standardsoftware anpassen

      2. Automatisierung

      3. Individualsoftware erstellen

    2. Grundprinzipien der Programmierung

      1. EVA- Prinzip

      2. Kommunizierende Objekte - das Paradigma der objektorientierten Programmierung

  2. C# Compiler

    1. Übersetzen auf der Kommandozeile

      1. Beispiel Hallo Welt

      2. Übersetzen

    2. Präprozessordirektiven

      1. #define

      2. #if (...) #else

      3. # region

  3. Aufbau eines C# Programms

    1. Literale

    2. Operatoren

      1. Geprüfte und Ungeprüfte arithmetische Operationen

      2. Operator overloading

      3. Indexer

    3. Variablen

      1. Deklaration in C#

      2. Initialisierung

      3. Typinferenz (ab NET 3.5)

      4. C# Datentypen

      5. Blockstruktur, Sichtbarkeit und Lebensdauer von Variablen

      6. Statische Variablen

      7. Typ bestimmen mittel is Operator

      8. Metainformationen zu einer Instanz

      9. Metainformationen zu einer Klasse mittels typeof – Operator

      10. Reflektion

      11. Aufgaben

      12. Implizite Typkonvertierung

      13. Explizite Typkonvertierung

      14. Konvertierungsfehler

      15. Konvertieren mittels as

      16. Selbstdefinierte Konvertierungsoperatoren

      17. Nullable Typen (NET 2.0)

    4. Elementare Ein/Ausgabe

      1. Formatzeichenfolgen

      2. Formatzeichenfolgen für nummerische Typen

      3. Formatzeichenfolgen für Datumstypen

      4. Formatzeichenfolgen für Aufzählungstypen

      5. ToString und Formatierung

      6. Ausgabe mit IformatProvidern

      7. Composit- Formating

    5. Steuerung der Programmausführung

      1. Bedingt ausführbarer Programmabschnitt

      2. Bedingt wiederholbarer Programmabschnitt

      3. for- Schleife

      4. for each – Schleife

    6. Funktionen und Prozeduren

      1. Mathematische Funktionen

      2. Call by value

      3. Call by Reference

      4. Ausgabeparameter

      5. Parameterarrays

      6. Überladen von Methoden

    7. Namespaces

      1. using- Direktive

    8. Enumerations

    9. Datenstrukturen

      1. Merkmale von Strukturen

    10. Datum und Uhrzeit

    11. Arrays

      1. Deklaration

      2. Zugriff

      3. Initialisierung von Arrays

      4. Anzahl der Einträge bestimmen

      5. Zuweisen und Kopieren

      6. Besuchen aller Einträge eines Arrays mittels foreach - Schleife

      7. Arrays sortieren

      8. Array mit selbstdefinierten Typen Sortieren

    12. char

      1. Objektmodell von char

      2. Char , Strings und Unicode

    13. Strings

      1. Teilstrings ausschneiden

      2. Umwandeln in Groß- oder Kleinschreibung

      3. Startposition von Teilstrings bestimmen

      4. Einfügen in Strings

      5. Auftrennen von Zeichenfolgen

      6. Testen mittels regulärer Ausdrücke

      7. Splitten mittels Reguläre Ausdrücke

      8. Ersetzen mittels Regulärer Ausdrücke

      9. Implentierungsdetails von Strings

Einführung

Historische Entwicklung


Ziel der Programmierung

Standardsoftware anpassen

Beispiel: EXCEL wird als Kalkulationssoftware eingesetzt. Bestimmte Kalkulationen sollen online auf einem Webserver veröffentlicht werden, indem Inhalte aus Tabellenzellen in Datenbanktabellen auf dem Webserver übertragen werden. Für den Anwender soll die Übertragung genauso einfach wie ein Speichern des Arbeitsblattes sein. Als Werkzeug bietet sich hier VB und VBA an.

Automatisierung

Sich wiederholende, komplexe Arbeitsabläufe können in einem Script erfasst, und auf Knopfdruck gestartet werden. Beispiele: Backup von Datenbanken/Festplatten, Wiederherstellen von Arbeitsoberflächen (Anmeldescripte). Als Werkzeuge bieten sich hier Scriptsprachen wie PERL und VBScript an.

Individualsoftware erstellen

Auf einer Plattform (PC oder Microcontroller) wird eine völlig neue Anwendung erstellt. Als Werkzeuge bieten sich hier Entwicklungsumgebungen wie Visual C++ oder Visual.NET an. Dies ist der kompliziteste Fall. Die Entwicklung von Individualsoftware kann sogar zur Entwicklung völlig neuer Programmierwerkzeuge führen (UNIX-> C, DHTML-> JavaScript)

Grundprinzipien der Programmierung

EVA- Prinzip




Kommunizierende Objekte - das Paradigma der objektorientierten Programmierung

Die Welt besteht aus Objekten, die miteinander kommunizieren. Die Kommunikation erfolgt über Botschaften, die in den meisten objektoriantierten Sprachen als Methoden realisiert sind. Botschaften können Produkte von Ereignissen im System sein wie z.B. das Anklicken einer Schalftfläche durch einen Benutzer. Anderseits können Botschaften programmgesteuert an Objekte gesendet werden. Damit lassen sich Verarbeitungsformen wie Stapelverarbeitung (übergabe der Kontrolle an ein weiteres Objekt) und Client/Server (Aufrufen eines Dienstes im Objekt) realisieren.

Erstmals realisiert wurde die objektorientierte Programmierung in den Sprachen Simula und Smalltalk.


C# Compiler

Im folgenden werden die Stufen der Übersetzung eines C#- Programms dargestellt:




Übersetzen auf der Kommandozeile

Im Folgenden wird ein einfaches Programm erstellt, welches auf der Kommandozeile den String "Hallo Welt" ausgibt. Anhand des Programmes wird die Kompilation in MSIL sowie das Kompilat selbst untersucht.

Beispiel Hallo Welt

Legen sie eine Datei namens Hallo.cs an.

=> Quelltexte werden in Unicode- Dateien mit der Endung cs gespeichert.

// C# Kennenlernen
// Kommentare sind einzeilig und werden mit einem Hochkomma (') eingeleitet
// Alle Prozeduren und Funktionen müssen in einem Modul-
// oder Klassenblock eingeschlossen werden
class CHallo {

    // Die Prozedur Main hat eine besondere Bedeutung: Beim Programmstart 
    // wird vom Lader des Betriebssystems an diese die Kontrolle übergeben
    public static void Main() {

        // Hier wird ein Unterprogramm aus der Bibliothek System.dll aufgerufen
        // Mittels des Unterprogramms Console.WriteLine erfolgt die Ausgabe
        // der Zeichenkette "Hallo Maja" auf der Kommandozeile
        System.Console.WriteLine("Hallo Maja");
    }

}

Übersetzen

Das Beispielprogramm kann auf der Kommandozeile (Visual- Studio- Tools/Command Prompt) durch folgenden Befehl kompiliert werden:

c:\cs-kurs\csc hallo.vb /reference:System.dll

Die Option /reference informiert den Compiler , das aus der System.dll Metainformationen zu beziehen sind.

Präprozessordirektiven

Mittels Präprozessordirektiven können Abschnitte von der Kompilation ausgeschlossen werden.

#define

Mit #define kann eine Konstante definiert werden, die in Präprozessorblöcken für die bedingte Kompilation ausgewertet wird. Die Konstanten müssen als erste Anweisungen in einer Quelltextdatei definiert werden.

#define XX_GRUSS_MAJA
Class CHallo

   public static void Main() {
       :
   }
}

Hier wird eine Konstante namens XX_GRUSS_MAJA deklariert.

#if (...) #else

In Abhängigkeit von bedingten Konstanten können Abschnitte im Quelltext von der Kompilation ausgeschlossen werden oder nicht:

#define XX_GRUSS_MAJA
#define XX_KEIN_GRUSS
class CHallo {

    public static void Main() {

#if (XX_GRUSS_MAJA && !XX_KEIN_GRUSS)
        System.Console.WriteLine("Hallo Maja");
#elif (!XX_GRUSS_MAJA && !XX_KEIN_GRUSS)
        System.Console.WriteLine("Hallo Marina");
#endif

#if (XX_KEIN_GRUSS)
        #error Kein Gruss
#endif

   }
}

In diesem Beispiel wird die Zeile zwischen #if ... #endif nicht kompiliert. Beim kompilieren wird zudem die Fehlermeldung Kein Gruss generiert. Wenn die zweite Zeile (#define XX_KEIN_GRUSS) auskommentiert wird, dann erfolgt keine Kompilation für die Zeile zwischen #if ... #else.

# region

Dient zur logischen Gliederung des Quelltextes. Die IDE kann Regionen wahlweise auf- und zuklappen. Bsp.:

#region "mm"
  sub p1
   ...
  end sub
  sub p2
   ...
  end sub
#end region

Aufbau eines C# Programms

C# ist eine vollständig objektorientierte Programmiersprache. Im Mittelpunkt der Progammierung mit C# steht

  1. die Beschreibung des Aufbaus von Objekten durch Klassendeklarationen

  2. das Senden von Nachrichten an Objekte durch Methodenaufrufe

  3. die Reaktion auf asynchrone Nachrichten/Ereignisse durch Ereignisse

Die gesamte Logik muß in Klassendeklarationen verpackt werden.

Folgendes Beispiel zeigt ein einfaches Befehlszeilenprogramm.

using System;

// Namensraum der Anwendung
namespace m1_hallo
{
        #region "HAllo"
        /// <summary>
        /// Zusammenfassung für Class1.
        /// </summary>
        class Class1
        {
                /// <summary>
                /// Der Haupteinstiegspunkt für die Anwendung.
                /// </summary>
                [STAThread]
                static void Main(string[] args)
                {
                        //
                        // TODO: Fügen Sie hier Code hinzu, um die Anwendung zu starten
                        //

                        Console.WriteLine("Hallo Welt, es ist {0} Uhr", System.DateTime.Now.ToShortTimeString());
                        Console.ReadLine();
                }
        }

        #endregion
}

Literale

Menge

Beispiel

Anmerkung

ganze Zahlen, dezimal

123


ganze Zahlen, Hexadezimal

0xAF


negative Zahlen

-123


rationale Zahl (doppelt genau)

123.4

oder 123.4d


rationale Zahl (einfach genau)

123.4f


rationale Zahl (super genau)

123.4m


rationale Zahlen in Exponentialschreibweise

1.23e4


Zeichen

'A'


Zeichen als Unicode- Referenz (hexadezimal)

'\u0058'

Escapesequenz \u als Präfix für Unicode- Nummer

Zeichenketten

"ABC\n"

In Zeichenkettenliterale werden Escapesequenzen wie \n durch die entsprechenden ASCII- Steuerkodes wie CF+LF ersetzt. Soll ein \ ausgegeben werden, dann muss er durch die besondere Escapesequenz \\ dargestellt werden.

literale Zeichenketten

@"\s+XYZ"

In literalen Zeichenketten findet keine Ersetzung von Escapesequenzen statt. Sie sind damit besonders zur Darstellung von regulären Ausdrücken geeignet.

Wahrheitswerte

true oder false


Nullwert

null


Achtung: Für Datumswerte gibt es in C# keine Literale. Datumswerte werden vollständig durch die .NET- Klasse DateTime abgebildet.

Operatoren


Operatorkategorie

Operatoren

Arithmetisch

+   -   *   /   %

Logisch (boolesch und bitweise)

&   |   ^   !   ~   &&   ||   true   false

Zeichenfolgenverkettung

+

Inkrementieren, Dekrementieren

++   --

Verschieben

<<   >>

Relational

==   !=   <   >   <=   >=

Zuweisung

=   +=   -=   *=   /=   %=   &=   |=   ^=   <<=   >>=

Memberzugriff

.

Indizierung

[]

Typumwandlung

()

Bedingt

?:

Delegatverkettung und -entfernung

+   -

Objekterstellung

new

Typinformationen

as   is   sizeof   typeof   

Überlaufausnahmesteuerung

checked   unchecked

Dereferenzierung und Adresse

*   ->   []   &

Geprüfte und Ungeprüfte arithmetische Operationen

try {
  int x = int.MaxValue;
  checked {
           x++;
         }
} catch (OverflowException ex) {
   Console.WriteLine(ex.Source + "hat einen Überlauf veursacht");
}

Operator overloading

Operatoren können nur als statische Methoden deklariert werden. Prototypen:

// Operator- Overloading (unäre Operatoren)
public static <ResultType> operator <OpSymbol> (Operand1)

// Operator- Overloading (binäre Operatoren)
public static <ResultType> operator <OpSymbol> (Operand1, Operand2)

Indexer

Indexer entsprechen der Operatorüberladung von [] in C++. Sie sind stets an Instanzen gebunden, können also nie statisch deklariert werden.

public <ResultType> this[<indexType> i]

Variablen

Unsere Programme realisieren Algorithmen, mit denen wir Daten verarbeiten. Um Zugriffe auf die Daten im Computerspeicher zu formulieren, muß uns die Programmiersprache entsprechende Ausdrucksmittel zur Verfügung stellen.

Ein elementares Ausdrucksmittel für Speicherzugriffe sind Variablen.

Unter Variablen können wir uns benannte Speicherplätze vorstellen. Variablen sind immer vor ihrer Verwendung im Programmtext mittels einer Deklaration zu vereinbaren. In der Deklaration wird der zu verwendende Name für den Speicherplatz und der Datentyp festgelegt.

Deklaration in C#

[Zugriffsmodifikator] [Modifikator] Typ Variablenname;

Der Zugriffsmodifikator steuert den Zugriff auf eine Variable innerhalb ihres Gültigkeitsbereiches. Durch den Typ wird festgelegt, welchen Datentyp die Variable hat.

Mögliche Modifikatoren: const, readonly, static

Mögliche Zugriffsmodifikatoren: private, protected, public, internal

Initialisierung

Für Variablen in statischen Methoden erfolgt eine automatische Initialisierung. Variablen in nichtstatische Methoden müssen manuell initialsiert werden:

int i = 0;

Typinferenz (ab NET 3.5)

Mit Typinferenz wird das Verfahren bezeichnet, aus dem Wert, mit dem eine Variable initialisiert wird, den Typ für die Deklaration der Variable abzuleiten.

Typinferenz wurde im Zusammenhang mit Linq eingeführt, da der Datentyp des Ergebnisses einer Linq- Abfrage sich häufig erst aus der Abfrage ergibt.

Typinferenz sollte nur in Ausnahmefällen eingesetzt werden, schließlich ist die Wahl eines Datentypen für eine Variable eine Entscheidung, die bewusst vom Programmierer getroffen werden muss.

// Typinferenz: Deklaration ohne Typspezifikation-> Typ wird aus dem zugewiesenen Wert bestimmt
var i = 0;
var str = "Hallo Welt";

// i ist nach wie vor streng typisiert
i = "Hallo Welt";    // => Fehler, da i vom Typ int

C# Datentypen

Die primitiven Typen sind im Namespace System definiert:

C#

CTS

Wertebereich

Literale

Anmerkungen

bool

System.Boolean

{true, false}

true

Wertetyp

sbyte

System.SByte

[-128, 127]

99

Wertetyp

byte

System.Byte

[0, 255]

255

Wertetyp

char

System.Char

[0, 65535]

'A'

Wertetyp

short

System.Int16

[-32768, 32767]

199

Wertetyp

ushort

System.UInt16

[0, 65535]

199

Wertetyp

int

System.Int32

[-2147483648, 2147483647]

199

Wertetyp

uint

System.UInt32

[0, 4294967295]

199 oder 199U

Wertetyp

long

System.Int64

[-9223372036854775808, 9223372036854775807]

199 oder 199L

Wertetyp

ulong

System.UInt64

[0, 18446744073709551615]

199 oder 199UL

Wertetyp

float

System.Single

[-3.402823E+38, 3.402823E+38]

3.14 oder 3.14F

  • Wertetypen

  • Gleitpunktliterale sind per Default vom Typ double

  • Division durch 0 führt zu Werten wie +unendlich, -unendlich und NAN

double

System.Double

[-1.79769313486232E+308, 1.79769313486232E+308]

9.0 oder 9D

decimal

System.Decimal

[0, 79.228.162.514.264.337.593.543.950.335]

125.99M

  • Wertetyp

  • decimal kann nur explizit in einen double oder float konvertiert werden

string

System.String

0- 2 Mrd. Unicodezeichen

"Hallo Welt"

Referenztyp

-

System.DateTime

00001-1-1 0:0:0 bis 9999-12-31 23:59:59

 

Referenztyp

object

System.Object

 

 

Referenztyp

Blockstruktur, Sichtbarkeit und Lebensdauer von Variablen


Statische Variablen

In Sonderfällen kann es sinvoll sein, die Lebensdauer von Variablen in Funktionen und Prozeduren über die Blockgrenzen hinaus zu verlängern. Soll z.B. Protokolliert werden, wie oft eine Methode in einem Programm schon aufgerufen wurde, dann muß eine Zählervariable bereitgestellt werden, die wie eine Globale Variable zu Programmstart initialisiert und erst bei Programmende vernichtet wird. Erreicht wird dis durch den Zugriffsmodifizierer static. Statische Variablen dürfen in C# nur auf der Ebene von Klassendeklarationen vereinbart werden:

class CUtils {   

   static long id = 0;

   long MakeID() {
     id += 1;
     return id;
   }

}

Typ bestimmen mittel is Operator

Mittels des is Operators mit kann zu einem Objekt der Typ bestimmt werden wie folgt:

if (x is int) {
   ....
}

Metainformationen zu einer Instanz

Jedes Objekt erbt von der .net Wuzel System.Object die Methode GetType(). Diese Liefert eine Referenz auf ein Type- Objekt, welches alle Metainformationen zum Objekt enthält.

Metainformationen zu einer Klasse mittels typeof – Operator

Die Klassenmethode System.Type.GetType(object Classname) bestimmt für einen Typ (Klassenname) das Type- Objekt, ohne daß ein Instanz notwendig ist.

Mittels des typeof- Operators kann zu einer Klasse ein Typ- Objekt erzeugt werden. Er entspricht der statischen Methode System.Type.GetType(..).

Reflektion

Die in den Type- Objekten gespeicherten Metainformationen beschreiben die Struktur einer Klasse vollständig. Beispielsweise können alle Member einer Klasse wie folgt aufgelistet werden:

using System;
using System.Reflection;


class CTest {
  int ganzZahl;
  enum EPartei {gut, boese, gerecht};
  int TueWas() {
     ganzZahl ++;
     return ganzZahl;
  }
}

:
void main() {

  // Typinfo abfragen (Reflektion)
  Type t = typeof(CTest);

  // Alle Felder, Eigenschaften und Methoden werden aufgelistet                 
  MemberInfo[] minfos = t.GetMembers();
  foreach (MemberInfo minfo in minfos) 
  {
      Console.WriteLine("{0}", minfo.ToString());
  }
} 

Aufgaben

  1. Entwickeln Sie eine Klasse CTypeBrowser, welche die Methode info(object) implementiert, die zu einem gegebenen Objekt alle Felder und Methoden auflistet.

Implizite Typkonvertierung

Implizite Typkonvertierungen sind zwischen verwandten Datentypen möglich, wenn der Zieltyp mehr Informationen aufnehmen kann als der Quelltyp:


Explizite Typkonvertierung

Explizite Typkonvertierungen erzwingen die Umwandlung von einem Typ in einen anderen. Dabei gibt es jedoch verschieden starke Konvertierungsoperatoren. Auf der einen Seite gibt es die C#- Konvertierungsoperatoren, auf der anderen Seite die Operatoren der Klasse Convert.

Syntax der Konvertierungsoperatoren:

(Typname) Objekt

Beipspiel:

decimal decPi = 3.1415926535897932384626433832795M;
float   fltPi = (float) decPi;

Die Convert Funktionen sind am stärksten Konvertierungsfunktionen. Sie ermöglichen eine Konvertierung zwischen an sich inkompatibelen Typen wie Zahlenwerte in Strings in nummerische Werte von Int32 etc.

string strPreis = "199,00";
decimal decPreis = Convert.ToDecimal(strPreis);

Konvertierungsfehler

Ist eine Explizite Konvertierung nicht möglich, dann wird eine Ausnahme vom Typ InvalidCastException geworfen:

try {
   string txt = "hallo";
   object refX = txt;

   // Folgende Konvertierung führt zu einer Ausnahme  
   int x = (int) refX;
} catch (InvalidCastException ex) {
    Console.WriteLine(ex.Message);
}

Konvertieren mittels as

Referenztypen können in C# mittels des as – Operators konvertiert werden. Dieser löst im Falle einer nichtdurchführbaren Konvertierung keine Ausnahme aus, und entspricht damit in seiner Wirkungsweise folgendem Code:

refX is TypeX ? (TypeX)refX : null;

Beispiel:

string txt = "Hallo";
object refX = txt;

Exception ex = refX as Exception;

Selbstdefinierte Konvertierungsoperatoren

Für selbsdefinierte Typen können spezielle Konvertierungsoperatoren implementiert werden:

class CMy {

   public int x;


   // Deklaration eines impliziten Konvertierungsoperators
   public static implicit operator int(CMy obj) {
       return obj.x;
   }

   // Deklaration eines expliziten Konvertierungsoperators
   public static explicit operator string(CMy obj) {
      return obj.x.ToString();
   }
}

:

// Einsatz
CMy mx = new CMy();
my.x   = 99;
int i  = my;
string txt_i = (string) my;

Nullable Typen (NET 2.0)

Die Datentypen, die in Datenbanken verwendet werden besitzen gegenüber den Datentypen einer Programmiersprache wie C# einen besonderen Zustand: den Null- Wert. Dieser tritt auf, wenn eine Tabellenzeile angelegt- , aber nicht allen Spalten gleichzeitig ein Wert zugewiesen wurde.

Um die Programmierung von Datenbanken mit C# zu erleichtern wird ab .NET 2.0 ein allgemeines Framework zum Umgang mit solchen Null- Typen bereitgestellt. Kern ist hierbei die generische Struktur System.Nullable<T>:


C# vereinfacht den Umgang mit Nullable- Typen, indem es für Sie eine Spezielle Syntax anbietet:

// Deklaration eines Nullable Typs
Nullable<int> x;
// Deklaration in vereinfachter C#- Syntax mittels ? Postfix
int? y;

// Auslesen eines Wertes aus einem Nullable Typen, wobei im Falle eines null- Wertes
// ein Standardwert ausgegeben wird
int u = x.GetValueOrDefault(99);

// Der gleiche Vorgang in vereinfachter C# Syntax
int v = x ?? 99;

…

void queryMethod1(out int? CountRows) {
   SqlCommand cmd("select count(*) from tab1", "");
   CountRows = cmd.ExecuteNonQuery();
}

…
int? CountR;
queryMethod1(out CountR);

Elementare Ein/Ausgabe

Beispiel:

decimal preis = 99.45;
// Preis wird als Währungswert formatiert
Console.WriteLine("Preis: " + preis.ToString("C"));

Formatzeichenfolgen

Über Formatzeichenfolgen wird abhängig vom Datentyp die Darstellung eines Wertes als String gesteuert. Es gibt Formatzeichenfolgen für:

Formatzeichenfolgen für nummerische Typen

Formatzeichenfolgen bestehen aus einem Formatbezeichner und optional aus der Angabe einer Genauigkeit XX. Um eine Zahl mit max. 3 Nachkommastellen auszugeben, kann folgende Formatzeichenfolge verwendet werden:

N3

Folgende Formatbezeichner sind vordefiniert:

Formatbezeichner

Bedeutung

Beispiel

Resultat

C

Währungsbetrag

(99.490).ToString("C");

99,49 €

D

Ganzzahliger Wert. Die Genauigkeit definiert die minimale Ausgabebreite. Sollte die darzustellende Zahl die minimale Ausgabebreite nicht erreichen, dann wird mit führenden 0-en aufgefüllt

(123).ToString("D5");

00123


E

Zahl in Exponentialschreibweise

(99.49).ToString("E");

9,95E+005

F

Festkomma. Die Genauigkeit gibt eine feste Anzahl von Nachkommastellen an

(99.49).ToString("F1");

99,5

G

Allgemein- Formatierung erfolgt abhängig vom Typ der Zahl

(99.49).ToString("G");

99,5

N

Zahl (allgemein). Die Genauigkeit beschreibt die Anzahl der Nachkommastellen

(99.49).ToString("N");

99,49

P

Prozentzahl. Der Wert wird mit 100 multiplizieret und mit einem Prozentzeichen versehen

(0.15).ToString("P");

15,00%

H

Hexadezimal

(0xA1).ToString("H");

A1

R

Stellt Zahlen so als Zeichenkette dar, daß sie aus diesen ohne Genauigkeitsverlust wieder zurückkonvertiert werden können.

(99.49).ToString("R");

99,49

Neben vordefinierten Formatzeichenfolgen können Formate wie im obigen Beispiel auch selbst definiert werden. Siehe dazu: Benutzerdefinierte Formatzeichenfolgen

Formatzeichenfolgen für Datumstypen

Formatbezeichner

Bedeutung

Beispiel

Resultat

d

Kurzes Datumsformat

DateTime.Now.ToString("d");

27.04.06

D

Langes Datumsformat

DateTime.Now.ToString("D");

27. April 2006

T

Zeitformat

DateTime.Now.ToString("T");

12:17:59

s

ISO 8601 konformes Datumsformat. Werte sind sortierbar. Dieser Typ wird z.B. in XML- Schema angewendet

DateTime.Now.ToString("s");

2006-04-27T12:17:47

Formatzeichenfolgen für Aufzählungstypen

Formatbezeichner

Bedeutung

Beispiel

Resultat

G

Stellt Wert eines Aufzählungstyps als String dar, falls möglich. Andernfalls wird der Wert als Integer- Zahlenwert dargestellt

(SUnits.cm).ToString();

"SUnits.cm"

F

Stellt Wert eines Aufzählungstyps als String dar, falls möglich. Andernfalls wird der Wert als Integer- Zahlenwert dargestellt

(SUnits.cm).ToString();


D

Stellt Wert eines Aufzälungstyps als dezimalen Integer dar

(SUnits.cm).ToString();

2

X

Stellt Wert eines Aufzälungstyps als hexadezimalen Integer dar

(SUnits.cm).ToString();

0x2

ToString und Formatierung


Ausgabe mit IFormatProvidern

using System.Globalization;

// Kopie des aktuell wirksamen Formatproviders erzeugen
NumberFormatInfo nif= (NumberFormatInfo)System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.Clone();

// Währungssysmbol auf Dollar umstellen
nif.CurrencySymbol = "$";

// Ausgabe eines Währungswertes
double preis = 99.99;
Console.WriteLine(preis.ToString("C", nif));    

Composit- Formating

In Methoden wie Console.WriteLine("Formatstring", object, ...) können Textkonstanten und Formatblöcke, die Formatzeichenfolgen für auszugebende Parameter enthalten, kombiniert werden. Dies wird als Composit- Formating bezeichnet. Soll z.B. ein Längenmaß ausgegeben werden, welches aus dem Wert und der Längeneinheit besteht, dann könnte dies durch folgende Anweisung erfolgen:

Console.WriteLine("Längenmaß: {0,5:N} {1,2}", LMasz.value, LMasz.unitToString());

Ein Formatblock hat folgende Syntax:

{ParamListenIndex[,alignment][:Formatzeichenfolge]}

Der ParamListenIndex bezeichnet den Parameter aus der Parameterliste, dessen Wert durch den Parameterblock formatiert werden soll. Das Alignment definiert die Ausrichtung und mindestbreite. Positive Werte stehen für eine Rechtsausrichtung, negative für eine Linksausrichtung.

Steuerung der Programmausführung

In seiner Grundbetriebsart lädt der Computer eine Anweisung nach der anderen aus dem Arbeitsspeicher und führt sie aus. Viele Algorithmen erfordern jedoch, daß einige Abschitte aus den Anweisungslisten nur unter bestimmten Bedingungen auszuführen sind (z.B. „Wenn bis zum Tag X kein Zahlungseingang erfolgte, dann Mahnung abschicken“) oder wiederholt werden müssen. Um dies zu ermöglichen, verfügt der Computer über spezielle Anweisungen, die den Befehlszählerstand manipulieren. Sie bewirken, daß nach ihrer Ausführung das Programm an einer beliebigen neuen Adresse fortgesetzt wird. Aufgrund dieser Wirkung werden sie Sprunganweisungen genannt.

In VBA sind die Sprunganweisungen in Form von bedingt ausführbaren, oder bedingt wiederholbaren Programmabschnitten implementiert.

Bedingt ausführbarer Programmabschnitt

Der bedingt ausführbare Programmabschnitt entspricht einer Wenn ... dann Regel.

if (Bedingung ) {
   Anweisung 1;
   Anweisung 2;
       :
   Anweisung n;
}

Trifft die Bedingung nicht zu, dann kann anschließend mit dem Schlüsselwort Else ein Programmabschnitt als Alternative formuliert werden:

if ( Bedingung ) {
   Anweisung 1;
       :
   Anweisung n;
} else {
   Anweisung n+1;
       :
   Anweisung n+m;
}

Gibt es mehrere Alternativen, die von Zusatzbedingungen abhängig sind, dann können diese in ElseIf- Blöcken formuliert werden:

if ( Bedingung ) {
   Anweisung 1;
       :
   Anweisung n;
} else if ( Bedingung2 ) {
   Anweisung n+1
       :
   Anweisung n+m
}

Ist die Ausführung mehrerer Programmabschnitte vom Wert eines einzigen Ausdrucks abhängig, dann kann alternativ zu ElseIf eine vereinfachte Schreibweise mittels Select Case Block gewählt werden:

switch (Ausdruck) {
 case Wert1:
      Anweisung 1;
         :
      Anweisung n;
      break;
 case Wert2:
      Anweisung n+1;
         :
      Anweisung n+m;
      break;
 case Wert3:
   :
 default: {
           Anweisung
          }
}

Bedingt wiederholbarer Programmabschnitt

Es handelt sich hierbei um Programmabschnitte, die sooft wiederholt werden, solange eine Bedingung zutrifft (wahr ist). Sie werden gewöhnlich als Schleifen bezeichnet. Zwei elementare Formen der Schleifen werden unterschieden: die abweisende, und die nichtabweisende Schleife.

Bei der abweisenden Schleife wird stets am Anfang des Programmabschnittes geprüft, ob die Bedingung für eine Ausführung des Abschnittes erfüllt ist. Wenn ja, dann wird der Programmabschnitt ausgeführt, und am Ende der Befehlszähler wieder auf die Startadresse des Programmabschnittes gesetzt. Gilt die Bedingung weiterhin, dann erfolgt eine wiederholte Ausführung des Programmabschnittes. Trifft die Bedingung nicht zu, dann wird der Programmabschnitt übersprungen.

while(Bedingung) {
   Anweisung 1;
   Anweisung 2;
       :
   Anweisung n;
}

Bei der nichtabweisenden Schleife wird die Bedingung erst am Ende des Programmabschnittes überprüft. Trifft sie zu, dann wird der Befehlszähler auf den Abschnittanfang gesetzt, und dieser wiederholt. Sonst wird mit der nachfolgenden Anweisung fortgesetzt.

Da die Bedingung erst am Ende des Abschnittes überprüft wird, wird der Abschnitt immer mindestens einmal ausgeführt.

do {
   Anweisung 1
   Anweisung 2
       :
   Anweisung n
} while (Bedingung);

for- Schleife

Die for- Schleife wird klassisch als zählergesteuerte Schleife betrachtet. Moderner ist es sie als Rahmen für komplexe Iterationen durch Aufzählungen zu betrachten (wie Arrays etc.)

for ( <Anweisung bei Schleifenstart>;
      <Bedingung für jeden Schleifendurchlauf>;
      <Anweisung nach jedem Schleifendurcjlauf> ) {
   Anweisung 1;
   Anweisung 2;
       :
   Anweisung n;
}

for each - Schleife

Ein Spezialfall der for- Schleife ist eine Interation durch eine Aufzählung, bei der jedes Element der Aufzählung besucht wird. Dieser häufige Spezialfall wird in c# durch eine for each Schleife formuliert:

int[] primz= {3, 5, 7, 11, 13, 17};
foreach int p in primz {
   Console.WriteLine(p.ToString() + '\n');
}

Funktionen und Prozeduren

Häufig benutzte Programme wie z.B. Mehrwertsteuerberechnung etc. können in sog. Prozeduren bzw. Funktionen verpackt werden. Prozeduren und Funktionen werden in C# einheitlich in einem Funktionsblock implementiert. Die Unterscheidung zwischen Funktion und Prozedur erfolgt hier über den Rückgabetypen:

Funktion vs. Prozedur

Prozeduren geben keinen Wert zurück. Sie haben den Rückgabetyp void.

Funktionen geben einenWert von einem Typen T zurück.



Ein Funktionsblock gliedert sich in:

Funktionskopf

Funktionsrumpf

Der Funktionskopf legt einen Namen und den Typ des Rückgabewertes für die Funktion fest. Weiter wird im Funktionskopf die Parameterliste definiert. Über die Parameterliste erfolgen die Eingaben. Ausgaben können über die Parameterliste oder den Rückgabewert eines Funktionsblocks erfolgen:

<Rückgabetyp> <Unterprogrammname> ([ref|out] <Paramtyp> <Paramname>, ...) { Funktonsrumpf }

Im Funktionsrumpf befinden sich die Anweisungen, welche die Funktion/Prozedur implementieren. Ein Funktionsrumpf ist ein gewöhnlicher Programmblock, also eine Liste von Anweisungen, die zwischen geschweiften Klammern eingeschlossen ist.

Beispiel:

float MwSt(float nettopreis, char warengruppe)
{
   float satz_a, satz_b;

   satz_a = 0.07;
   satz_b = 0.16;

   if (warengruppe = 'a')
      return nettopreis * satz_a;
   else
      return nettopreis * satz_b;
}   

Mathematische Funktionen

Die wichtigsten mathematischen Funktionen sind im Math- Objekt des Namespace System enthalten.

Call by value

Ohne weitere Kennzeichnung werden alle Paramter beim Aufruf einer Funktion als Kopien der Orignale des Aufrufers übergeben:

int AddToStatic(int x) {

    // x enthält eine Kopie auf den Wert x aus dem Aufrufer
    static int akku = 0;
    akku += x;
    x++;

    return akku;
}


static void Main() {

   int x = 1;

   // Der Aufruf von AddToStatic ändert den Wert von x nicht
   Console.WriteLine(AddToStatic(x));

   x = 10;
   Console.WriteLine(AddToStatic(x));

} 

Call by Reference

Um Wertetypen durch einen Call by Reference zu übergeben, gibt es das Schlüsselwort ref. Es muß in der Parameterliste und in beim Aufrug angegeben werden !

void mm_in_cm(float mm, ref float cm) {
    cm = mm / 10F;
}

// Hauptprogramm
float cm = 0;
mm_in_cm(1000F, ref cm);

Ausgabeparameter

Die Richtung des Datenflusses kann bei Parametern auch nur auf die Ausgabe von Werten beschränkt werden. Dies ist ein Spezialfall des Call bey Reference. Gegenüber Call by Reference muß die als Argument übergebene Variable im Aufrufer nicht initialisiert sein.

void mm_in_cm(float mm, out float cm) {
    cm = mm / 10F;
}

// Hauptprogramm
float cm;
mm_in_cm(1000F, out cm);

Parameterarrays

Mittels des Schlüsselwortes params kann eine Funktion eine Parameterliste mit beliebig vielen Elementen erhalten:

public long summe_aus(params int[] liste) {
   long summe;
   foreach(long x in liste)
       summe += x;
   }
   return summe;
}

Überladen von Methoden

class zahl 
{
   public double mul(int a, int b) 
   {
      return a * b;
   }

   public double mul(float a, float b)
   {
     return a * b;
   }
}
// Hauptprogramm
zahl z = new zahl();
double x;
x = z.mul(2, 3);
x = z.mul(3.14F, 2F);

Namespaces

Namespaces dienen zur Abgrenzung der Namensmengen zwischen verschiedenen Teilen von Bibliotheken und Programmen.

namespace outer {

  namespace inner {

     public int x;
  }

}

// Zugriff auf x
outer.inner.x = 99;

using- Direktive

using Namespace;
using Alias = Namespace;

Enumerations

Endliche Mengen lassen sich durch Aufzählungen modellieren (Enumarations). Beipiel:

enum enumLaengenEinheiten {mm, cm, dm, m, km, AE, LichtJahr}

Datenstrukturen

Datenstrukturen dienen zur Modelierung zusammengesetzter Datentypen. Im Unterschied zu Klassen sind sie Wertetypen, dh. ihre Instanzen werden auf dem Stack allokiert.

struct Laenge 
{                       
  public float   wert;
  public enumLaengenEinheiten einheit;
}

Merkmale von Strukturen

Aufg.:

Erzeugen Sie ein Array aus Strukturtypen und ein Array aus Klassentypen. Versuchen Sie zur Laufzeit auf die Einträge der Arrays zuzugreifen.

Datum und Uhrzeit

Grundlage für die Datumsberechnung ist ein linearer Zeitstrahl, der in Ticks unterteilt ist. Ein Tick hat die Dauer von 10-7s (zehnmillionstel Sekunde). Zeitpunkte auf dem Zeitstrahl werden durch Werte vom Typ Date angegeben. Ein Datumswert wird hat das Format #M/D/YYYY# (amerikanisches Datumsformat).


Date basiert auf System.DateTime. Die Konstruktoren von System.DateTime sind mehrfach überladen:

DateTime dat1 = new DateTime(Ticks)
DateTime dat2 = new DateTime(YYYY, MM, DD)
DateTime dat3 = new DateTime(YYYY, MM, DD, hh, min, sec, millisec)

Zur Darstellung von Zeiträumen dient die Klasse TimeSpan.

Arrays

Zur Verarbeitung tabelarischer Daten dienen in C# Arrays. Durch die Deklaration eines Arrays werden im RAM n Speicherplätze zur Aufnahme von Werten des Typs x reserviert.

Arrays sind Referenztypen.

Alle Arrays sind von der Klasse System.Array abgeleitet. Diese dient C# intern als Basisklasse zur implementierung der Arrays. Von System.Array kann nicht abgeleitet werden !

Deklaration

Deklaration eines Arrays hat folgenden Aufbau:

int[] a = new int[4];
 |    |           +------------- Anzahl der Einträge
 |    +--------------- Name des Arrays
 +------------------ Typ der Einträge (Array)
double[,] tabWegZeit = new int[2, 10];  // Zweidimensionales Array

Array sind Referenztypen. Nach einer Arraydeklaration ergibt sich folgendes Format im Arbeitspeicher:


Zugriff

Der Zugriff auf die Elemente eines Arrays erfolgt über den Namen und den Indize:

int x;

// x hat nach Ausführung dieser Anweisung den Wert 2
x = a[2]
    | +--- Indize (Platznummer)
    +----- Arrayname

// Dokumentation der Bewegung eines Körpers von 500m in 300 s in einem 2D- Array
tabWegZeit[0, 1] = 300; // 300 s
tabWegZeit[1, 1] = 500; // 500 m

Die Indizes können dynamisch zur Laufzeit festgelegt werden. Damit ist eine komfortable Verarbeitung der Arraydaten in Schleifen möglich:

int i;
long s;

// s enthält nach der Schleife die Summe aller Einträge
for (int i = 0; i < a.length; i++)
   s = s + a[i];

Initialisierung von Arrays

int[] primz = {2, 3, 5, 7, 11};

Anzahl der Einträge bestimmen

int anz_elems = tabWegZeit.length;  // Anzahl der Elemente in über alle Dimensionen
int anz_dims  = tabWegZeit.rank;    // Anzahl der Dimensionen eines Arrays
int anz_elem_in_dim_1 = tabWegZeit.GetLength(1)  // Anzahl der Elemente in Dimension 1

Zuweisen und Kopieren

Durch Zuweisung wird nur eine Referenz erzeugt, jedoch keine Kopie.

int[] primz2 = primz;

Die Methode Clone() eines Arrays erzeugt eine 1:1 Kopie

int[] primz3 = (int[])primz.Clone();

Besuchen aller Einträge eines Arrays mittels foreach - Schleife

foreach (int zahl in primz) {
   Console.WriteLine(zahl);
}

Arrays sortieren

string[] planeten = {"Merkur", "Venus", "Erde", "Mars", "Jupiter", "Saturn"};
Array.Sort(planeten);

Array mit selbstdefinierten Typen Sortieren

enum ELaenge {mm, cm, dm, m, km};

struct Laenge : IComparer
{
  public float  wert;
  public enumLaengenEinheiten einheit;

  public int Compare(object obj1, object obj2) 
  {
    Laenge l1 = (Laenge)obj1;
    Laenge l2 = (Laenge)obj2;

    // alles in mm umrechnen
    float il1 = tomm(l1), il2 = tomm(l2);
    if (il1 == il2)
      return 0;
    else if (il1 < il2)
      return -1;
    else 
      return 1;         
  }

  float tomm(Laenge l) 
  {
     switch (l.einheit) {
         case ELaenge.mm:
            return l.wert;
         case ELaenge.cm:
            return l.wert * 10;
         case ELaenge.dm:
            return l.wert * 100;
         case ELaenge.m:
            return l.wert * 1000;
         case ELaenge.km:
            return l.wert * 1000000;
     }
     return -1;
  }
}
// In Main
Laenge[] fahrten = new Laenge[3];
fahrten[0].einheit = ELaenge.mm;
fahrten[0].wert = 100000;
fahrten[1].einheit = ELaenge.cm;
fahrten[1].wert = 3000;
fahrten[2].einheit = ELaenge.km;
fahrten[2].wert = 0.99F;

Array.Sort(fahrten, fahrten[0]);

foreach (Laenge fahrt in fahrten) 
{
   Console.WriteLine("{0} {1}", fahrt.wert, fahrt.einheit.ToString());
}

Aufg.:

  1. Min/Max- Suche,

  2. Sortieren

  3. Messwerterfassung (akt. Wert, dyn. Mittelwert)

char

Objektmodell von char


Char , Strings und Unicode

.NET unterstützt Unicode. Quelltexte können als Unicode- Dateien verpackt werden, und Zeichentypen wie char und string basieren auf 16bit Unicode. Folgendes Windows- Programm liefert einen Dialog mit einem kyrillischen Text:


public class Form1 : System.Windows.Forms.Form {

#Region " Vom Windows Form Designer generierter Code "
  :
#End Region

    private Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) 

        // char verarbeiten Unicode- Zeichen
        char c1, c2, c3, c4, c5, c6, c7;

        c1 = '\u041B';
        c2 = '\u042E';
        c3 = '\x0411';
        c4 = '\x041E';
        c5 = '\x0412';
        c6 = '\x042C';

        string str;

        str += c1.ToString() + c2.ToString() + c3.ToString() + c4.ToString() + c5.ToString() + c6.ToString();

        lblDasWichtigste.Text = str;


    }
}

Strings

Siehe .NET Doku

Teilstrings ausschneiden

string txt = "Anton, Berta, Cäsar";
Console.WriteLine(txt.Substring(8, 5));

Umwandeln in Groß- oder Kleinschreibung

string txt = "Hallo Welt";

Console.WriteLine("Alles groß: {0}", txt.ToUpper);
Console.WriteLine("Alles klein: {0}", txt.ToLower);

Startposition von Teilstrings bestimmen

string txt = "Anton, Berta, Cäsar";
int pos = txt.IndexOf("Berta");

Einfügen in Strings

string txt = "Anton, Berta, Cäsar";
txt = txt.Insert(txt.IndexOf("Berta"), "Aladin, ");

Auftrennen von Zeichenfolgen

string txt = "Anton,Berta,Cäsar";
string[] namen = txt.Split(',');

Testen mittels regulärer Ausdrücke

Das folgende Prädikat testet, ob ein Ausdruck dem Muster eines GUID entspricht

public static bool IsGuid(string expr)
{
    if (expr != null)
    {
      Regex guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$");
      return guidRegEx.IsMatch(expr);
    }
    return false;
}



Splitten mittels Reguläre Ausdrücke

System.Text.Regex r = new Regex("\\s*=\\s*");
string [] aw = r.Split("Durchmesser = 199");
Console.WriteLine ("Attribut {0} hat Wert {1}", aw[0], aw[1]);

Ersetzen mittels Regulärer Ausdrücke

// In folgendem String sollen alle Vorkommen von keinen durch [] markiert werden
String txt = "Das Pferd frisst keinen Gurkensalat. Ich habe keine Ahnung warum";
Regex r = new Regex();

string txt_neu = r.Replace(txt, keinen,  "[" + keinen + "]")

Implentierungsdetails von Strings

Strings werden in .NET trickreich verwaltet, um den Resourcenbedarf zu minimieren. Dabei werden die String- Werte in einer Hashtable abgelegt. Wenn mehrere Stringvariablen den gleichen Wert haben, dann teilen sie sich alle denselben Eintrag in der Hashtable. Änderungen am Inhalt einzelner Stringvariablen werden durch Anlegen neuer Einträge in der Hashtable mit den Ergebnissen der Manipulation realisiert. Aus diesem Grunde ist eine direkte Manipulation der Stringinhalte nicht möglich- alle Stringfunktionen lassen das Orginal unberührt und liefern einen neuen String mit dem Ergebnis der Manipulation

// Strings sind Referenztypen
string s1 = "Hello World";

// Kopieren von Referenzen: s1 und s2 zeigen auf das gleiche Objekt
string s2 = s1;             

Trace.WriteLineIf(object.ReferenceEquals(s1, s2), "&s1 == &s2");

// Besondere Speicherverwaltung für Strings: anstatt mehrere Kopien
// des gleichen Strings wird intern in einer Hashtable auf den gleichen
// Eintrag verwiesen
string s3 = "Hello World";
Trace.WriteLineIf(object.ReferenceEquals(s1, s3), "&s1 == &s3");

// Werden Strings verändert, dann legt die Laufzeit für den geänderten einen
// neuen Eintrag in der Hashtable an
s3 += "!";
Trace.WriteLineIf(object.ReferenceEquals(s1, s3), "&s1 == &s3 + \"!\"");

// Stringbuilders scheinen immer einen neuen Eintrag in der Hashtable 
// anzulegen, unabhängig davon, ob der Wert schon existiert
System.Text.StringBuilder bld = new StringBuilder(10);
bld.Append("Hello ");
bld.Append("World");

string s4 = bld.ToString();
Trace.WriteLineIf(object.ReferenceEquals(s1, s4), "&s1 == &s4");

// Gegenprobe: Neue Strings mit "alten Werten" verweisen wieder auf den
// alten Eintrag in der Hashtabelle
string s5 = "Hello World";
Trace.WriteLineIf(object.ReferenceEquals(s1, s5), "&s1 == &s5");
Quellen
  1. Bart de Smet: .NET 2.0 string interning inside out

  2. Wesner Moise: strings UNDOCUMENTED

Aufgaben

  1. Romzahlkonverter

  2. Zählen der Buchstabenhäufigkeit in gegebenen Texten