.NET-Code, unfreiwilliger Open Source?

Es ist schon ein wenig gewöhnungsbedürftig, der Gedanke, dass der gesamte Code eines mühevoll entwickeltes Programms quasi von jedem Nutzer eingesehen werden kann. „Open Source“ ist an dieser Stelle sicherlich zu viel gesagt, denn nur weil der Quellcode (mit Hilfsmitteln) einsehbar ist, heißt dies nicht, dass dieser auch für andere Zwecke als die Programmausführung genutzt werden darf.

Aber ein einsehbarer Quellcode erleichtert die Spionage, Piraterie und sonstigen Missbrauch enorm.
Aber warum kann der Quellcode eines .Net Programms so einfach eingesehen werden? Das Problem existiert bei .NET Sprachen (und auch Java) zum einen, weil die Quellcode in eine Zwischensprache (IL, Intermediate Language) übersetzt wird und zum anderen an Reflections.

Die Zwischensprache ist bereits für den Menschen lesbarer als Assemblercode und lässt sich verhältnismäßig einfach wieder zurück übersetzen, denn die Zwischensprache spricht anders als Assemblercode nicht einzelne Register an (kann sie auch nicht, denn sie ist eine hardware-universelle Sprache), sondern liest sich auf einem viel höheren Abstraktionslevel.

→ WEITERLESEN

Reflections in C#

Es kann zur Laufzeit eines Programms notwendig sein, dass das Programm sich selbst analysieren und ggf. verändern kann. In C# kann dies mir der Funktionalität von Reflections erreicht werden.

Reflection „reflektiert“ zur Laufzeit aufrufbare Assemblys, Module und Typen und bietet so dynamischen Zugriff auf Objekte, Methoden und Metadaten, welche erst zur Laufzeit bekannt werden.
Eine Programmiersprache, welche diese Möglichkeit zur „Reflektion“ der eigenen Programminhalte hat, kann ihre eigene Struktur selbst zur Laufzeit analysieren und auf sie Einfluss nehmen.

Reflection als Methode zum dynamischen Aufruf von Klassen, Methoden, Variablen, Ressourcen usw. ist abgesehen von C# (und anderen .NET-Programmiersprachen) auch in Java bekannt und in noch einigen weiteren Programmiersprachen in ähnlicher Form verbreitet. Aber auch die Reflection-Funktionalität von Java und C# sind nicht absolut identisch. Während Java auf Klassenebene „reflektiert“ und generell auch Klassen als Informationsempfänger verwendet, kommt C# nicht über Klassen, sondern über Assemblies an die gewünschten Informationen heran.

Grundsätzlich sollte der Einsatz von Reflections vermieden werden, denn der dynamische Aufruf ist wesentlich langsamer, als der direkte Aufruf. Insgesamt ist der Einsatz dieser Funktionalität mit übermäßigen Performance-Einbußen verbunden. Mit der Anwendung von Reflections erkauft sich der Programmierer Flexibilität zur Laufzeit, nimmt damit aber Leistungseinbußen in Kauf.
Nur wenn ein dynamischer Aufruf unbedingt notwendig ist, gibt es für Reflections grundsätzlich keine Alternative. Beispiele für Anwendungsgebiete sind Schnittstellen für Plugins, der Debugger und auch die automatische Textvervollständigung in Entwicklungsumgebungen (z. B. Microsofts IntelliSense).

→ WEITERLESEN

Ü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.

→ WEITERLESEN

Kategorien C#

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.

[csharp] System.Type type = typeof(double);
[/csharp]

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.

[csharp] double d = 5.4;
System.Type type = d.GetType();
[/csharp]

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.

→ WEITERLESEN

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:

Klassendiagramm

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.

→ WEITERLESEN

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?“.

Use Case Diagramm

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.

→ WEITERLESEN

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

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.

→ WEITERLESEN

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.