Grundsätzliches in C#

using ... ;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string strTest = "";
            Console.WriteLine("Geben Sie einen Testwert ein: ");
            strTest = Console.ReadLine();
            Console.WriteLine("Ausgabe Testwert: " + strTest );
            Console.ReadKey();
        }
    }
}

Variablen in C#

  • Eine Variablen wird z. B. wie folgt definiert und initialisiert: int iZahl = 0;
  • Eine Array wird z. B. wie folgt definiert und initialisiert : int[] array1 = new int[5] { 1, 3, 5, 7, 9 };

Operatoren in C#

  • Arithmetische: +, -, /, *, %
  • Zuweisung: =, +=, -=, /=
  • Vergleich: ==, >, <, >=, <=, !=
  • Inkrement, Dekrement: ++, --
  • Logische: &&, ||
  • Zeichenkettenverknüpfung: + (z. B. strZeichenkette1 + strZeichenkette2)

Kontrollstrukturen in C#

if(Ausdruck)
{
    Anweisungen
}
switch(Ausdruck){
    case Wert1:
        Anweisungen
        break;
    case Wert2:
        ...
    default:
        ...
}
while(Ausdruck)
{
    Anweisungen
}
do {
    Anweisungen
} while(Ausdruck);
for(Ausdruck1; Ausdruck2; Ausdruck3)
{
    Anweisungen
}
foreach (Element in Feld)
{
    Console.WriteLine(Element + "\n");
}

Funktionen in C#

Funktionsdefinition bei „call by value“

static int ZahlenwertInkrementieren(int iLokaleZahl)
{
    return ++iLokaleZahl;
}

Funktionsaufruf bei „call by value“

iZahl = ZahlenwertInkrementieren(iZahl);

Funktionsdefinition „call by reference“

static void ZahlenwertInkrementieren(ref int iLokaleZahl)
{
    iLokaleZahl++;
}

Funktionsaufruf „call by reference“

ZahlenwertInkrementieren(ref iZahl);

Werttypen und Verweistypen in C# im Zusammenhang mit Call by Value bzw. Call by Reference

Was sind Werttypen?

Bei Werttypen wird der zu speichernde Wert direkt in einer Variable des entsprechenden Datentyps gespeichert.

Beispiel: int iZahl = 427;

Zu den Werttypen zählen die Standard-Datentypen wie int, float, double, bool und char.

Übergabe eines Werttypen an eine Funktion (Methode) per Call by Value (CBV)

class MainClass
{
    public static void Werttyp_CallByVale(int iParam)
    {
        iParam += 173;
    }
    
    public static void Main(string[] args)
    {
        int iZahl = 427;
        
        Console.WriteLine("Inhalt von iZahl vor dem Funktionsaufruf (CBV): {0}", iZahl);
        Werttyp_CallByValue(iZahl);
        Console.WriteLine("Inhalt von iZahl nach dem Funktionsaufruf (CBV): {0}", iZahl);
    }
}

Der Quellcode generiert die folgende Ausgabe:

Inhalt von iZahl vor dem Funktionsaufruf (CBV): 427
Inhalt von iZahl nach dem Funktionsaufruf (CBV): 427

Beim Aufruf per CBV arbeitet die Funktion mit einer Kopie des Originals, die über den Parameter iParam zugänglich gemacht wird:

AWP - CBV 01

iZahl und iParam sprechen also zwei verschiedene Speicherorte an! Deshalb wirkt sich die Änderung des übergebenen Wertes nicht auf das Original des Hauptprogramms aus, weil die Änderung nur an der Kopie vorgenommen wird!

Übergabe eines Werttypen an eine Funktion1 / Methode per Call by Reference (CBR)

class MainClass
{
    public static void Werttyp_CallByReference(ref int iPaaram)
    {
        iParam += 173;
    }
    
    public static void Main(string[] args)
    {
        int iZahl = 427;
        
        Console.WriteLine("Inhalt von iZahl vor dem Funktionsaufruf (CBR): {0}", iZahl);
        Werttyp_CallByValue(ref iZahl);
        Console.WriteLine("Inhalt von iZahl nach dem Funktionsaufruf (CBR): {0}", iZahl);
    }
}

Der Quellcode generiert die folgende Ausgabe:

Inhalt von iZahl vor dem Funktionsaufruf (CBR): 427
Inhalt von iZahl nach dem Funktionsaufruf (CBR): 600

Beim Aufruf per CBR arbeitet die Funktion mit einem Verweis auf das Original, der über den Parameter iParam zugänglich gemacht wird:

AWP - CBV 01

Es ist so, als würde man direkt in das Original schreiben; iParam ist jetzt nur ein anderer Name (Alias), über den man auf das Original zugreift. Somit wirkt sich die Änderung innerhalb der Funktion direkt auf das Original des Hauptprogramms aus. iZahl und iParam sprechen ein- und denselben Speicherort an.

Was sind Verweistypen?

Verweistypen sind Variablen von Datentypen, über die nur über einen Verweis zugegriffen werden kann. Es handelt sich also um einen indirekten Zugriff auf den Speicher. Zu den Verweistypen zählen Klassen, Enumerationen (Aufzählungstypen) und Arrays.

Zur Verdeutlichung soll zunächst die Klasse CPupil deklariert werden:

class CPupil
{
    public int iID = 0;
    public string strName = "";
    public string strVorname = "";
}

Nun wird innerhalb eines Hauptprogramms ein Objektverweis erstellt; anschließend wird diesem Objektverweis der Speicherort eines neu erzeugten Objektes zugewiesen. Schließlich erfolgt eine Änderung des Objektes über den Objektverweis:

CPupil onePupil; // Verweis vom Typ CPupil
onePupil = new CPupil; // Erzeugung eines Objektes
onePupil.iID = 10; // Änderung des Objektes mithilfe des Objektverweises onePupil

Für das Verständnis wichtig ist die Erkenntnis, dass Objektverweis und Objekt zwei verschiedene Variable sind, die zudem innerhalb verschiedener Speicherbereiche liegen.

Der Objektverweis befindet sich i.d.R. im Stack, also im Speicherkontext einer Funktion (innerhalb main( ) oder irgendeiner anderen Funktion), während das Objekt selbst im Heap liegt!

AWP - CBV 01

Veränderungen des Objekts sind grundsätzlich nur über den Objektverweis (hier: onePupil) möglich, weil das (dynamisch erzeugte) Objekt gar keinen Namen trägt.

Übergabe eines Objekttypen an eine Funktion (Methode) per Call by Value (CBV)

class MainClass
{
    public static void Werttyp_CallByValue(CPupil objParam)
    {
        objParam.iID = 25;
    }
    
    public static void Main(string[] args)
    {
        CPupil onePupil = new CPupil;
        onePupil.iID = 10;
        
        Console.WriteLine("Inhalt von iID vor dem Funktionsaufruf (CBV): {0}", onePupil.iID);
        Werttyp_CallByValue(onePupil);
        Console.WriteLine("Inhalt von iID nach dem Funktionsaufruf (CBV): {0}", onePupil.iID);
    }
}

Der Quellcode generiert die folgende Ausgabe:

Inhalt von iID vor dem Funktionsaufruf (CBV): 10
Inhalt von IID nach dem Funktionsaufruf (CBV): 25

Die Änderung des Objekts innerhalb der Funktion hat offensichtlich dazu geführt, dass die Änderung auch nach dem Funktionsaufruf wirksam bleibt. Dies scheint auf den ersten Blick im Widerspruch dazu zu stehen, dass der Parameter ein Call By Value Parameter ist. Um zu verstehen, was hier passiert, soll die folgende Skizze dienen:

AWP - CBV 01

Wichtig: Es wird nicht der Objektverweis geändert, sondern das Objekt, auf das der Objektverweis zeigt; deshalb funktioniert das mit Call By Value. Auf das Objekt im Heap zeigen also zwei verschiedene Objektverweise: der Verweis onePupil aus dem Hauptprogramm und der Verweis objParam innerhalb der Funktion. Über beide ist der Zugriff auf das Objekt möglich, so dass Änderungen des Objekts (das es nur ein Mal gibt!!) innerhalb der Methode auch nach dem Funktionsaufruf wirksam bleiben.

Es stellt sich nun die spannende Frage, ob man im Falle von Objekttypen also gar kein Call by Reference mehr braucht. Nun, solange man nur Änderungen an den Daten vornehmen will, auf die ein Objektverweis zeigt, kommt man mit Call by Value aus (siehe oben). Will man jedoch den Objektverweis selbst ändern, weil man beispielsweise innerhalb der Funktion ein Objekt erzeugen möchte, auf das das Hauptprogramm Zugriff haben soll, kommt man nur mit Call by Reference zum Ziel.

Übergabe eines Objekttypen an eine Funktion (Methode) per Call by Reference (CBR)

class MainClass
{
    public static void Werttyp_CallByValue(ref CPupil objParam)
    {
        objParam = new CPupil();
        objParam.iID = 25;
    }
    
    public static void Main(string[] args)
    {
        CPupil onePupil = null;
        
        Werttyp_CallByValue(ref onePupil);
        Console.WriteLine("Inhalt von iID nach dem Funktionsaufruf (CBR): {0}", onePupil.iID);
    }
}

Der Quellcode generiert die folgende Ausgabe:

Inhalt von iID nach dem Funktionsaufruf (CBR): 25

In diesem Beispiel wird die Objekterzeugung in die Funktion verlagert; dabei wird dem Objektverweis objParam ein neuer Wert zugewiesen. Die Änderung bezieht sich also auf den Wert des Parameters selbst. Und das hat nur dann Auswirkungen auf das Hauptprogramm und den dortigen Objektverweis onePupil, wenn der Parameter per Call by Reference übergeben wird!
Wie die Ausgabe des Programms zeigt, kann über den Objektverweis onePupil des Hauptprogramms auf das in der Funktion erzeugte Objekt zugegriffen werden.

Auch hierzu wieder eine Skizze:

AWP - CBV 01

Wie bei 1.2 (CBR bei Werttypen) wird auch hier der Inhalt des Parameters selbst verändert und nicht das Objekt, auf das der Parameter zeigt!

Grundsätzlich gilt also (egal ob bei Wert- oder Objekttypen): Call by Reference macht eine Änderung des vom Hauptprogramm an die Funktion übergebenen Parameters möglich, die auch nach Funktionsaufruf wirksam bleibt.

  1. Funktion und Methode werden innerhalb dieses Info-Skripts synonym verwendet; in beiden Fällen handelt es sich um ein Unterprogramm!