Überladen von Methoden
Im Laufe eines Software-Projekts sind Algorithmen zu entwerfen und in Methoden zu schachteln, welche zwar prinzipiell das gleiche tun, aber mit Werten von unterschiedlichen Datentypen oder einer verschiedenen Anzahl an Werten. Für solche Fälle bietet C# die Möglichkeit zur Überladung von Methoden.
Die meisten höheren Programmiersprachen erlauben überladene Methoden, neben C# beispielsweise auch C++ und Java.
Überladene Methoden haben zwei oder mehr Signaturen. Die Signatur ist die Kopfzeile einer Methode, welche die Zugriffsmöglichkeit, den Rückgabewert, den Methodennamen und die Parameter (Namen, Anzahl und Typ) beschreibt.
Die Überladung bezieht sich in C# (und den meisten anderen Programmiersprachen auch) jedoch ausschließlich auf die Parameteranzahl und die Datentypen der Parameter, denn nur diese sind bei einem Methodenaufruf maßgebend für eine klare Abgrenzung der verschiedenen Ausführungen einer überladenen Methode. (mehr…)
typeof() vs .GetType()
Um den Typen nach System.Type bestimmen zu können, werden entweder der Operator typeof() oder die Instanzmethode getType() verwendet. Diese beiden Möglichkeiten liefern ein Objekt vom Typ System.Type zurück.
Oft wird die Frage gestellt, welcher Unterschied zwischen diesen beiden Möglichkeiten der Typ-Bestimmung besteht.
Der typeof()-Operator kann nur das Objekt von System.Type eines Typen (eine Klasse) bestimmen.
(Achtung: In C# sind auch die oft als primitiven Typen bezeichneten Wertetypen, z. B. Integer (int) oder Double (double) Klassen!)
Dabei kann der typeof()-Operator auch generische Typen bestimmen. Überladen werden, kann dieser Operator jedoch nicht.
getType() ist eine Methode einer Instanz (eines Objekts). getType() kann daher nicht frei verwendet werden, wie der typeof()-Operator, sondern ist an eine Instanz gebunden. getType() liefert den Typen, der eigenen Instanz zurück.
-
double d = 5.4;
-
System.Type type = d.GetType();
getType() kann im Gegensatz zu typeof() auch überladen werden. GetType() kann allerdings nicht (typeof() natürlich auch nicht) überschrieben werden.
Auf den Punkt gebracht: Zur Bestimmung von Typen von Klassen wird der Operator typeof() benutzt. Die Bestimmung von Typen von Objekten ist hingegen die Methode getType() zu benutzen.
(mehr...)
Klassendiagramm (Class Diagramm)
Das Klassendiagramm ist wahrscheinlich eines der Diagramme, welches zum großen Erfolg von UML beigetragen haben.
Mit dem Klassendiagramm lassen sich die für ein Programm entworfenen Klassen mit Atrributen und Methoden sowie den Beziehungen der Klassen zueinander visualisieren.
Ein Klassendiagramm ist daher nur auf die objektorientierte Software-Entwicklung anwendbar.
Ein Klassendiagramm muss nicht den vollständigen Klassenentwurf wiedergeben, sondern kann sich auch nur auf einen Ausschnitt relevanter Klassen beziehen.
Beispiel:
Obiges Klassendiagramm zeigt die Beziehung zweier Klassen. Die linke Klasse Eurofighter steht in Beziehung mit einer anderen Klasse Rakete.
Die Klasse Eurofighter hat Attribute (obere, rote Liste), welche die Eigenschaften der Klasse beschreiben. Außerdem hat die Klasse Operationen (untere, grüne Liste), welche die möglichen Funktionen (Methoden) der Klasse zeigen.
Dass in die Klasse Rakete keine Attribute und Operationen eingetragen wurden ist nicht weiter tragisch. Oft wird der Fokus auf die relevanten Klassen gelegt und nur bedingt relevante Klassen vereinfacht dargestellt; das Abstraktionslevel wird nicht selten bewusst gebrochen.
Ein UML-Klassendiagramm dient primär der Planung und Konzeption, als Hilfestellung für Software-Entwickler.
Es gibt zudem Programme, welche ein, nach den Programmrichtlinien erstelltes, Klassendiagramm in Quellcode umsetzt. (mehr...)
Anwendungsfall-Diagramm (Use-Case)
Komplexe Programme bieten häufig unterschiedlichen Benutzern verschiedene Interaktionen an.
Dass ein Programm nur eine Art von Benutzer kennt, welcher das Programm nur starten muss und somit eine Kette von Aktionen auslöst bis sich das Programm automatisch beendet, kommt sehr selten und früher eher als heute vor (unabhängig davon, dass es immer diese Art von Programmen geben wird).
Bei der Entwicklung z.B. eine Office-Anwendung oder einer Shop-Software können sich die Entwickler schnell in einem "Wirrwar" aus Benutzer-Rechten verlieren, "Wer darf was und was eigentlich nicht?".
Im sogenannten Anwendungsfall-Diagramm (engl. Use-Case-Diagram) werden Benutzer (engl. User) als kleine Strichmännchen dargestellt und so im Diagramm als Akteur, der auf das Programm Einfluss nehmen kann, symbolisiert. Aktionen, wie z.B. Anzeigen, Drucken oder Suchen, werden i.d.R. als Ovale dargestellt.
Um ein Anwendungsfall-Diagramm besser zu gliedern, kann es mehrere Anwender-Bereiche haben, z.B. Administrator-Bereich und Gäste-Bereich. (mehr...)
Unified Modeling Language – UML
Die Unified Modeling Language (kurz: UML) ist eine eigene Sprache (von engl. "Language") zur Darstellung von Softwaremodellierung. UML ist einer der vielen Teilbereiche des Software Engineering.
UML ist jedoch genau genommen nicht eine Sprache oder ein Verfahren, sondern eine Sammlung an verschiedenen Verfahren, welche unterschiedliche Views (zu Deutsch: Ansichten, Perspektiven) auf eine Software-Architektur bieten.
UML ist keine technisch definierte Sprache, sondern eine standarisierte abstrakte Konzeption. UML hat mehrere Versionen durchlaufen, von der Version 1.0 bis (aktuell) zur Version 2.0.
UML-Diagramme sind ein wichtiges Hilfsmittel bei der Konzeptionierung und Dokumentation von komplexer Software. Nicht selten sind UML-Diagramme Teil des Lasten-/Pflichtenhefts. Mit UML-Diagrammen lassen sich schon vor der Umsetzung Konzeptionierungsfehler entdecken und beheben. Zusätzlich kann die Software-Architektur mittels UML auch Laien näher gebracht werden.
Hier soll auf die wichtigsten UML 2.0 Diagramme eingegangen werden:
Strukturdiagramme
- Klassendiagramm
- Objektdiagramm
- Kompositionsstrukturdiagramm
- Verteilungsdiagramm
Verhaltensdiagramme
- Aktivitätsdiagramm
- Anwendungsfalldiagramm
- Zustandsdiagramm
- Sequenzdiagramm
- Kommunikationsdiagramm
Ausnahme-Behandlung in C# (Exception-Handling)
Ein Programm, welches mit einer höheren Programmiersprache entwickelt wurde, interagiert mit seiner Umwelt, neben anderen Programmen vor allem mit dem Betriebssystem, über dieses auch mit der Hardware.
Während das Programm bis in das kleinste Detail durchdacht und nach unzähligen Tests auch (scheinbar) völlig fehlerfrei funktioniert, kann es bei der Interaktion zwischen dem Programm mit seiner Umwelt zu Problemen kommen.
Beispielsweise könnte es beim Öffnen einer Datei zu einem Fehler kommen, die Datei lässt sich nicht öffnen (obwohl das Programm vorher zumindest die Existenz der Datei festgestellt hatte). Möglicherweise lässt sich eine Datei auch nicht speichern, da der Speicherplatz nicht ausreicht, das Speichermedium entfernt wurde oder defekt ist. Nur einige von sehr vielen Beispielen von sogenannten Exceptions.
Einige Exceptions lassen sich bis zu einem bestimmten Grad vorbeugen; Beispielsweise lässt sich unmittelbar vor der Beanspruchung von Speicherplatz prüfen, ob der Speicherplatz ausreicht. Ob der Speicherplatz dann aber bei der Speicherung tatsächlich vorhanden ist und nicht schon von einem anderen Vorgang belegt wurde, ist erst bei der Speicherung feststellbar.
Mit einem bestimmten System lassen sich solche Fehler zwar nicht verhindern, jedoch behandeln.
Software-Entwickler sprechen hierbei von Fehlerbehandlung oder (besser:) Ausnahmebehandlungen, also die Behandlung von Ausnahmen (Exceptions).
Mit einer Ausnahmebehandlung, wie sie z.B. in C++, Java und C# möglich ist, können diese Fehler umgangen werden, so dass Programm nur die fehlerträchtige Funktion beendet, aber dennoch nicht komplett abstürzt. Das .NET Framework von Microsoft bietet beispielsweise ein einheitliches Fehlermanagement mit der Möglichkeit zur Ausnahmebehandlung. (mehr...)
Datensicherungsverfahren
Alle Datensicherungsverfahren haben den gleichen Zweck, sie dienen der Sicherung von Daten (Backup). Es gibt jedoch eine Unterscheidung der Datensicherungsverfahren in drei Datensicherungen, welche in ihrem Umfang unterschieden werden:
Vollständige Datensicherung
Werden sämtliche Daten gespeichert, handelt es sich um eine Vollsicherung. Diese könnte den gesamten Inhalt eines Datenträgers umfassen (inklusiver Betriebssystem etc.), oder ein bestimmter, abgegrenzter Datenbereich (z.B. eine Projektmappe oder ein Verzeichnis).
Vorteil: Eine vollständige Datensicherung ist unabhängig von anderen Datensicherungen.
Nachteil: Die Datensicherung ist ggf. sehr zeitintensiv und benötigt verhältnismäßig viel Datenspeicher. Geht die Datensicherung verloren, ist der Verlust enorm. Daher sollten Vollsicherungen redundant gehalten und an verschiedenen Orten archiviert werden.
Eine Vollsicherung ist jedoch unumgänglich und dient mindestens als Basis für die weitere Datensicherung.
Differenzielle Datensicherung
Es werden bei der differenziellen Datensicherung die aktuellen Daten mit den Daten dem Stand der letzten vollständigen Datensicherung abgeglichen. Der ersten differenziellen Datensicherung muss eine vollständige Datensicherung voraus gehen. Die differenzielle Datensicherung ist eine Teilsicherung.
Bei der differenziellen Datensicherung werden alle Daten gesichert, die sich seit der letzten Datensicherung geändert haben bzw. neu hinzugekommen sind.
Dabei sind der jeweiligen differenziellen Datensicherung die bereits durch andere differenzielle Datensicherungen gesicherten Daten unbekannt.
Jeder differenzielle Datensicherung umfasst daher mehr Daten als seine vorhergehende, sofern es eine gab und sich Daten seit der letzten Datensicherung geändert haben.
Vorteil: Eine vollständige Datensicherung und eine einzige (möglichst aktuelle) Teilsicherung nach dem Prinzip der differenziellen Datensicherung reicht für die Möglichkeit der vollständigen Datenwiederherstellung aus.
Nachteil: Datensicherung kann sehr groß werden und Dimensionen der letzten vollständigen Datensicherung annehmen oder diese überschreiten.
Der Nachteil gegenüber der inkrementellen Datensicherung tritt dann auf, wenn sich nie (oder nur bedingt) die gleichen Dateien ändern, sondern immer neue Dateien hinzukommen; Dann werden bei jeder Datensicherung die alten und die neuen Dateien gesichert.
Inkrementelle Datensicherung
Es werden bei der inkrementellen Datensicherung die aktuellen Daten mit den Daten dem Stand der letzten vollständigen Datensicherung oder Teilsicherung abgeglichen. Der ersten inkrementellen Datensicherung muss eine vollständige Datensicherung voraus gehen.
Vorteil: Die Datensicherung ist sehr klein und dementsprechend schnell geschehen.
Kommen ständig neue zu sichernde Dateien hinzu, werden nur die jeweils neuen (im Vergleich zur letzten Sicherung) Dateien gespeichert.
Ändern sich jedoch permanent die gleichen Dateien, hat die inkrementelle Datensicherung keinen Vorteil gegenüber der differenziellen Datensicherung.
Nachteil: Es werden alle Datensicherungen seit der letzten Vollsicherung benötigt, wenn alle Daten wieder hergestellt werden sollen. Geht eine Datensicherung verloren, handelt es sich um einen Teildatenverlust.
Rekursion in der Programmierung
Die Rekursion und Iteration sind zwei konkurrierende Art und Weisen der Wiederholung eines Vorgangs in der Programmierung.
Während die Iteration eine Menge an Vorgängen (typischerweise eingerückt in eine Schleife) wiederholt und ggf. die Anzahl der Wiederholungen zählt (For-Schleife), ist die Rekursion ein Funktionsaufruf der selben Funktion - d.h. eine Funktion ruft sich selbst auf.
I.d.R. wird Rekursion für Suchalgorithmen verwendet. Die rekursiv arbeitende Funktion wird dabei so oft von sich selbst aufgerufen, bis ein Suchmuster gefunden wurde oder die Suche aus anderen Gründen endet.
Genauso wie bei der Iteration, ist auch bei der Rekursion darauf zu achten, dass es eine Abbruchbedingung gibt - Sonst droht eine Endlosschleife.
Die Rekursion hat gegenüber der Iteration einige Nachteile. Die Rekursion ist langsamer, da bei jedem rekursiven Schritt eine Methode aufgerufen wird. Aus diesem Grund belegt die Rekursion außerdem mehr Speicherplatz.
Dennoch haben sich rekursive Methoden in bestimmten Bereichen durchgesetzt. Z.B. als eleganter Lösungsweg, um ein mathematisches Problem zu lösen. Auch bei der Suche in Binären Bäumen hat sich die Rekursion durchgesetzt, da über eine iterative Vorgehensweise sehr viele Schleifen notwendig wären.
Die Rekursion erleichtert die Nachvollziehbarkeit von komplexen Suchalgorithmen, für Neulinge ist die Rekursion jedoch anfangs nur schwer vorstellbar, jedoch auch nur eine Sache der Gewohnheit. Aber auch für geübte Programmierer, können besonders verschachtelte, rekursive Methoden schnell sehr verwirrend sein.
(mehr...)
Wertetyp vs Refernztyp
In der prozeduralen und objektorientierten Programmierung wird zwischen Wertetypen und Referenztypen unterschieden.
Beispielsweise sind in den Programmiersprachen C# und Java primitive Datentypen, Enumeratoren und Strukturen Wertetypen. Klassenobjekte sind hingegen Referenztypen.
In C/C++ dienen Pointer (Zeiger) als Refernztyp, welche die Adresse speichert, die auf einen veränderbaren Wert verweist. Über den Pointer kann die Adresse dann von Funktion zu Funktion überreicht werden.
So kann aus jeder Funktion heraus, die den Zeiger zur Verfügung hat, der Wert verändert werden, auf den die Adresse zeigt. Der geänderte Wert ist über diese Adresse wieder abrufbar, von allen Funktionen aus, die die Refernz bekommen haben. Ein Zeiger in C ist also eine Referenz.
Ähnlich verhält es sich bei Klassenobjekten in C# und Java. Für diese wird ein Speicherplatz im Heap des Arbeitsspeichers angefordert, welcher sich über die Speicheradresse ansprechen und verändern (bzw. überschreiben) lässt. Auch die Klassenobjekte in C++ werden im Heap gespeichert.
Der Heap kann beliebig angesprochen werden, jedoch muss der Speicher bei Bedarf geordert und spätestens bei Programmende wieder freigegeben werden.
In C++ muss sich darum der Programmierer selbst kümmern; C#, Java und die meisten objektorientierten Skriptsprachen haben hierfür eine automatische Speicherbereinigung (Garbage Collector).
Wertetypen werden nicht über eine Speicheradresse an eine Funktion übergeben, sondern als Wert. Diese Werte werden kopiert und auf den Stack im Arbeitsspeicher gelegt.
Je mehr die Funktionsaufrufe verschachtelt sind, um so größer wird auch der Stack.
Diese Vorgehensweise ist durchaus sinnvoll, denn auf den Stack kann zwar nicht beliebig, sondern nur über das aktuellste (oberste) Stack-Element zugegriffen werden, da jedoch auch vor allem die
Funktionsvariablen (Parameter) benötigt werden, erfolgt der Zugriff daher i.d.R. sehr schnell.
Bei jeder Parameterübergabe mit Wertetypen werden die Werte daher kopiert und der Stack wächst. Die Werte sind (zumindest noch bei Funktionsaufruf) redundant auf dem Stack.
Werden die Parameter in der Funktion verändert, hat dies keine Auswirkungen auf die Variablen, welche außerhalb der Funktion als Parameter übergeben wurden.
In einigen Fällen ist es vom Programmierer erwünscht, dass auch Wertetypen innerhalb der Funktion geändert werden können. Wertetypen können als Referenz übergeben werden. Dann sind diese in C/C++ als Pointer zu übergeben, in C# und Java mit dem Schlüsselwort ref.
Ein Beispiel-Programm mit Funktionsaufrufen und Parameterübergaben als Werte- und Referenztyp:
-
class Program
-
{
-
static void Main(string[] args)
-
{
-
}
-
-
public Program()
-
{
-
int a = 45;
-
int b = 34;
-
-
this.Sum(a, b);
-
Console.WriteLine("a = " + a.ToString() + "; b = " + b.ToString());
-
-
this.Sum2(ref a, ref b);
-
Console.WriteLine("a = " + a.ToString() + "; b = " + b.ToString());
-
}
-
-
// Normalfall: By Value - die Variable a kann nicht überschrieben werden
-
private void Sum(int param1, int param2)
-
{
-
param1 = param1 + param2;
-
}
-
-
// Sonderfall: By Reference - die Variable a wird überschrieben
-
private void Sum2(ref int param1, ref int param2)
-
{
-
param1 = param1 + param2;
-
}
-
}
Ausgabe:
a = 45; b = 34
a = 79; b = 34
Die Ausgabe des Programms zeigt, dass nur die Methode mit der als Referenz übergebenen Parameter die Variablen im Konstruktor verändert.
Alternativ zum Schlüsselwort ref kann auch das Schlüsselwort out verwendet werden.
Während ein mit ref bezeichneter Parameter vor Paramterübergabe definitiv initialisiert werden muss damit er verwendet werden kann, initialisiert sich ein mit out bezeichneter Parameter bei Methodenaufruf selber und muss daher nicht im Vorfeld initialisiert worden sein.
Hiervon abgesehen besteht kein wesentlicher Unterschied zwischen den Schlüsselwörtern ref und out, daher kann auch keine Überladung mit Unterscheidung dieser beiden Schlüsselwörter geschehen.
Boxing / Unboxing
Eine Konvertierung eines Wertetyps zu einem Referenztyp wird als Boxing bezeichnet. Unboxing ist die entgegengesetzte Konvertierung, also einer Referenz zu einem Wert. Dabei ist in C# und Java i.d.R. explizites Casting notwendig (Vorbestimmung des Datentyps zwischen einfachen Klammern).
-
int a = 1548; // Wertetyp
-
object o = a; // boxing
-
int b = (int)o; // unboxing (mit Casting)
Queue Datenstruktur in C#
Eine Queue (Warteschlange) ist eine First-in-First-out-(FIFO) Datenstruktur.
Die Datenstruktur Queue ist ein Stapel, auf welchem nach und nach Daten abgelegt werden.
Allerdings wird auf Elemente der Queue nur von unten zugegriffen. Das unterste Element ist das mit höchster Priorität, nur auf dieses als erstes hinzugefügte Element kann zugegriffen werden.
Ein Pointer (Zeiger) zeigt intern immer auf das Element, welches (relativ zu den anderen Elementen) als erstes in die Queue aufgenommen wurde.
Ein einfaches "Lösche aus der Queue" gibt es nicht, ein Element kann aus der Queue mit Peek() herausgelesen und mit Dequeue() herausgelesen und zugleich entfernt werden.
An das Warteschlangenende angereiht werden Elemente mit Enqueue();
Funktionsweise einer Queue an Hand eines praktischen Beispiels:
Stellen Sie sich vor, Sie müssten eine Software für ein Theater schreiben. Die Software soll Ticket-Bestellungen für
Vorführungen bearbeiten. Nun sind die Vorstellungen aber sehr schnell ausgebucht, so dass viele Tickets nicht mehr ausgestellt werden können. Bei der Ticket-Bestellung gilt jedoch "wer zu erst kommt, mal zu erst".
Die Daten aller eingehenden Bestellungen legen Sie in eine Queue (Warteschlange). Die Daten in der Queue werden vom ersten bis zum letzten Eintrag abgearbeitet, bis die Queue vollständig abgearbeitet wurde oder keine Plätze mehr verfügbar sind.
« vorherige Seite — Nächste Seite »