Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Monday, March 27, 2017

C# - 1




    Inhaltsverzeichnis

    C# Vortrag: Best Practices für Performance
    Die Performance beschreibt die Leistung bzw. die Effizienz mit der eine Aufgabe erfüllt wird. Im Falle von Performanceverbesserungen im C# Umfeld wird immer darauf abgezielt die Programmablaufzeiten oder den Speicherverbrauch eines Programms oder einer Methode bei gleichem Ergebnis zu minimieren. Die hier aufgeführten Szenarien sind nur ein kleiner Teil dessen was alles möglich ist um die Programmlaufzeiten und Speichernutzung zu verbessern.


    Verkettung von Zeichenketten (strings)
    Zeichenketten sind im Speicher sind unveränderbar. Das bedeutet, dass bei jeder Änderung eines Strings eine neue Zeichenkette im Speicher abgelegt wird. Da hierbei jedes mal neuer Speicher allokiert werden muss, ist diese Operation relativ aufwändig. Wird nun eine Verkettung von variablen Zeichenketten angestrebt und mit dem += Operator verkettet passiert folgendes:
    Wir gehen von dem unten gegebenen Code aus.
    string variableA = "String 1,";
    string variableB = "String 2,";
    string variableC = "String 3,";
    string variableD = "String 4,";
    string variableE = "String 5";
    string ergebnis = variableA + variableB + variableC + variableD + variableE;
    Beim Erstellen des Ergebnises haben wir bereits 5 Zeichenketten im Speicher. Wird nun eine Verkettung gemacht haben wir neben den 5 Zeichenketten folgendes im Speicher stehen:
    • Ergebnis von variableA + variable B,
    • Ergebnis von variableA + variable B + variableC, 
    • Ergebnis von variableA + variable B + variableC + variableD,
    • Ergebnis von variableA + variable B + variableC + variableD + variableE.
    Wir haben also durch die Verkettung nicht einfach das Ergebnis, sondern auch jedes nicht benötigte Zwischenergebnis gespeichert. Dadurch wurde jedes mal unnötig neuer Speicher allokiert.
    Um das Problem zu lösen gibt es in .NET unter dem Namespace System.Text die Klasse StringBuilder. Diese Klasse wurde speziell dafür geschrieben um variable Zeichenketten schnell zu verketten. Gerade bei Verkettung vieler variabler Zeichenketten oder bei Schleifenoperationen ist der StringBuilder sehr schnell, wie ein Performancetest zeigt. bei 50000 Verkettungen und 10 Iterationen braucht eine reguläre String-Verkettung mittel += rund 40 Sekunden. Der StringBuilder ist nach ~80ms fertig und zudem deutlich speicherschonender.
    Stringverkettung - Testcode
    public class StringConcat
    {
        private const int innerIterations = 50000;
        private const int outerIterations = 10;
      
        public void Concat()
        {
            for (int h = 1; h <= outerIterations; h++)
            {
                string concatResult = string.Empty;
                for (int i = 0; i < innerIterations; i++)
                {
                    concatResult += i.ToString();
                }
            }
        }
        public void ConcatStringBuilder()
        {
            for (int h = 1; h <= outerIterations; h++)
            {
                StringBuilder concatResult = new StringBuilder();

                for (int i = 0; i < innerIterations; i++)
                {
                    concatResult.Append(i);
                }
                concatResult.ToString();
            }
        }
    }
    Verkettung mit +=

    Verkettung mit StringBuilder



    Merke:
Tipp
Solange alle Zeichenketten, die verkettet werden sollen, eine konstante Länge aufweisen, können diese auch mit += verkettet werden. In dem Falle fasst .NET beim Kompilieren die Zeichenketten zu einer zusammen.
Externe Resourcen frühzeitig freigeben - IDisposable
In Anwendungen werden oftmals externe Resourcen genutzt. Dies kann zum Beispiel eine Datenbank sein, ein Filesystem, COM-Objekte oder auch ein Webservice. Bei Verwendung von externen Resourcen kann jedoch nie sichergestellt werden, dass diese fehlerfrei funktioniert oder überhaupt da ist. C# ist zwar eine Programmiersprache mit Managed Code und Garbage Collector, trotzdem führt schlechter Code auch hier zu Speicherlecks. Der Programmierer muss sich deshalb selbst um die Verwaltung der externen Resource kümmern, auf Fehler reagieren und die Resource zum Schluß wieder ordnungsgemäß freigeben. Tut er dies nicht ist die Folge das Blockieren einer Resource. Das kann weitreichende Auswirkungen haben. Unter Betrachtung der o.g. Beispiele wären das unter anderem:
  • nicht geschlossene Datenbankverbindung blockiert andere Anwendungen/Anwender der Datenbank
  • geöffnete Datei lässt sich nicht von weiteren Programmen öffnen/überschreiben
  • Objekte im Speicher verweilen dort auch nach Programmende. Erst ein Neustart des Betriebssystems sorgt dafür das diese verschwinden.
  • eine Verbindung zum Webservice bleibt bestehen und blockiert andere Anwendungen/Anwender des Webservices
  • etc.
In .NET gibt es zum Freigeben von Resourcen die Schnittstelle IDisposable. Wenn eine Klasse diese Schnittstelle implementiert, besitzt die Klasse die Methode Dispose(). Weiterhin kann eine Klasse, die diese Schnittstelle implementiert, über einen using-Block gesteuert werden. Dies sorgt dafür, dass am Ende eines using-Blocks die Methode Dispose() aufgerufen wird. Man könnte natürlich auch am Ende der Arbeit die Methode Dispose von Hand aufrufen. Dabei kann es bei schlechter Programmierung aber zum genannten Problem kommen. Folgender Code veranschaulicht dies.
Resource ohne using
try
{
    FileStream fileStream = new FileStream("C:\\Vortrag\\Outputfile.txt", FileMode.Create);
  
    StreamWriter streamWriter = new StreamWriter(fileStream);
    streamWriter.WriteLine("Hier stehen ganz viele Daten.");
  
    throw new InvalidOperationException("Fehler");
  
    streamWriter.Close();
    streamWriter.Dispose();
    fileStream.Close();
    fileStream.Dispose();
}
catch (Exception)
{
    //Hier wird die Ausnahme behandelt.
}
finally
{
    //Hier wird finaler Code ausgeführt
}

Wir öffnen eine Textdatei um etwas darin zu ergänzen. Es tritt dabei ein unerwarteter Fehler auf. Durch diese Ausnahme wird in den catch-Block gesprungen, ein Close() oder Dispose() auf die Objekte findet nicht statt. Die Datei kann somit nicht mehr verändert werden, da auf ihr noch eine Sperre liegt. Durch die Verwendung von using kann dies verhindert werden:

Resourcen mit using
try
{
    using (FileStream fileStream = new FileStream("C:\\Vortrag\\Outputfile.txt", FileMode.Create))
    {
        using (StreamWriter streamWriter = new StreamWriter(fileStream))
        {
            streamWriter.WriteLine("Hier stehen ganz viele Daten.");
            throw new InvalidOperationException("Fehler");
        }
    }
}
catch (Exception)
{
    //Hier wird die Ausnahme behandelt.
}
finally
{
    //Hier wird finaler Code ausgeführt
}

Tritt nun innerhalb eines eines using-Blocks eine Ausnahme auf, wird für beide Blöcke die Methode Dispose() aufgerufen, die die Resourcen wieder freigibt. Manchmal ist es auf Grund der Programmstruktur nicht immer möglich ein using zu verwenden. In dem Falle müssen die benötigten Klassen vor dem try-Block instanziiert und im finally-Block freigegeben werden.

Dispose mit try-finally
FileStream fileStream = new FileStream("C:\\Vortrag\\Outputfile.txt", FileMode.Create);
StreamWriter streamWriter = new StreamWriter(fileStream);
  
try
{
    streamWriter.WriteLine("Hier stehen ganz viele Daten.");
    throw new InvalidOperationException("Fehler");
}
catch (Exception)
{
    //Hier wird die Ausnahme behandelt.
}
finally
{
    streamWriter.Close();
    streamWriter.Dispose();
    fileStream.Close();
    fileStream.Dispose();
}
Merke:
  • Wann immer möglich sollten externe Resourcen, die IDisposable implementieren, mit "using" behandelt werden.
  • Bei Verwendung externer Klassen oder denen des .NET Frameworks muss immer geprüft werden ob sie die Schnittstelle IDisposable implementiert.
  • Beim Verwenden der Schnittstelle in eigenen Klassen muss man sich in der Dispose-Methode selber um das Freigeben von Resourcen und Schließen von Verbindungen kümmern.
Tipp
Es empfiehlt sich externe Resourcen so spät wie möglich zu öffnen/verwenden und so früh wie möglich wieder freizugeben.Gerade bei Anwendungen mit vielen Benutzern kann dies einen großen Performanceunterschied machen.


Boxing/Unboxing vermeiden - Equals erneut implementieren
Unter Boxing versteht man die Umwandlung eines Wertetyps in den Typ System.Object. Unboxing ist der umgekehrte weg von einem Object in einen anderen Typ. Beide Operationen sind sehr CPU und zeitlastig und sollten deshalb nur verwendet werden, wenn es unbedingt benötigt wird. Zudem wird für diese Operation mehr Speicher benötigt, da ein zusätzlicher Zeiger (Pointer) zu der Referenz benötigt wird und der Wert zusätzlich auf dem Heap abgelegt wird.
Ältere .NET Klassen wie z.B. ArrayList und HashTable arbeiten intern mit der Klasse System.Object. Beim Aufruf der Add-Methode von einer der beiden Klassen wird jedes mal eine Boxing-Operation durchgeführt. Zur Verbesserung der Performance sollten diese Klassen deshalb besser vermieden werden. Stattdessen ist es vorzuziehen generische Klassen mit Typsicherheit zu verwenden wie z.B. List<T> oder Dictionary.
Boxing / Unboxing
int myValue = 12;

//Boxing
object obj = myValue;

if (obj is int)
{
    //Unboxing
    int getMyValue = (int)obj;
}
Wann immer man mit der Klasse System.Object in Methoden arbeitet kann man davon ausgehen, dass hier eine Boxing/Unboxing Operation stattfindet. Manchmal hat man als Programmierer jedoch die Möglichkeit dieses Problem zu umgehen. Wenn man beispielsweise eine Methode überlädt, so  kann man hier eine eigene Implementierung mit einem typisiertem Objekt schreiben und diese Operation somit umgehen um noch etwas Performance zu gewinnen. Ein Beispiel hierfür wäre z.B. die Überladung der oft verwendeten Equals-Methode, die jedes Objekt besitzt und zwei Objekte vergleicht. Bei einer Überladung würde das .NET Framework dann auf die typsichere Methode zurückgreifen, wodruch man unnötiges Umwandeln (Unboxing) und Abfragen des Ergebnisses spart. Dies kann gerade bei vielen Aufrufen der Methode ein entscheidender Performancefaktor sein. Die Equals-Methode ist dabei nur ein Beispiel für eine mögliche Optimierung. Folgender Codeblock veranschaulicht die beschriebene Verbesserung:
Equals verbessern
// normale Equals Implementierung mit Unboxing
public override bool Equals(object obj)
{
    if (obj == null)
    {
        return false;
    }
    // Hier muss eine Umwandlung stattfinden und diese auch geprüft werden
    Person person = obj as Person;
    if (person == null)
    {
        return false;
    }
    return person.PersonID == this.PersonID;
}
  
// verbesserte Equals Implementierung ohne Unboxing
public bool Equals(Person obj)
{
    if (obj == null)
    {
        return false;
    }

    // Umwandlung (Unboxing) und Prüfung nach Umwandlung können hier entfallen
    return obj.PersonID == this.PersonID;
}
Merke:
  • Vermeide die Klassen ArrayList und HashTable -> Stattdessen List<T> und Dictionary mit sicheren Typen verwenden (nicht List<object>, sondern List<Klasse>)
  • Vermeide Boxing/Unboxing wenn möglich um Rechenzeit und Arbeitsspeicher zu sparen
  • Versuche Methoden mit Parameter System.Object zu verbessern, wenn diese oft verwendet werden


Exceptions vermeiden
Die Exception-Klasse beinhaltet sehr viele Informationen über aufgerufene Methoden, intern ausgelöste Ausnahmen, verwendete Typen etc. Diese Informationen werden über Traces und Reflection mitgeloggt. Der Aufbau dieses Objektes und das Abrufen dieser Informationen ist relativ aufwändig. Wird eine Methode häufig aufgerufen oder die Anwendung von vielen Benutzern gleichzeitig verwendet, kann es hier schnell zu Performanceeinbußen kommen, da der gesamte StackTrace für jeden Aufruf/Benutzer mitgeloggt werden muss. Es empfiehlt sich daher möglichst auf Exceptions zu verzichten. Das soll keinesfalls heißen, dass man gänzlich darauf verzichtet, sondern eher, dass man sie als das betrachtet was der Name schon impliziert: eine Ausnahmesituation. Bei vielen Operationen ist durch vorherige Prüfung oder bessere Programmierung das Abfangen einer Ausnahme unnötig. Die beiden folgende Beispiele verdeutlichen dies. Es gilt auch iher, dass es sich nur um Beispiele handelt und es noch mehr Möglichkeiten Exceptions zu vermeiden.
Ausnahmen vermeiden - NullReferenceException
// Es wird davon ausgegangen, dass eine Suche oder Operation stattgefunden hat, bei der eine Person gesucht wurde
Person person = null;
try
{
    // Zugriff auf Objekt null löst die Ausnahme aus
    int id = person.PersonID;
}
catch (NullReferenceException)
{
    //Ausnahme behandeln
}
// besser -> vor dem Zugriff das Objekt auf null prüfen!
Person person = null;
if(person != null)
{
    int id = person.PersonID;
}
else
{
    // Fehler behandeln
}
Ausnahmen vermeiden - InvalidCastException (Konvertierung von Datentypen)
// Unwandeln direkt über int.Parse und fangen der Ausnahme ist nicht effektiv
try
{
    string wert = "abc";

    int intWert = int.Parse(wert);
}
catch(InvalidCastException)
{
    // Ausnahme behandeln
}
// besser -> TryParse Methoden verwenden
string wert = "abc";
int intWert;
if (!int.TryParse(wert, out intWert))
{
    //wenn es nicht geklappt hat
}
Merke:
  • Ausnahmen wann immer es möglich ist vermeiden und im Vorfeld zu überlegen ob sie durch vernünftige Prüfungen ersetzt werden kann
  • Ausnahmen sollten auch Ausnahmen bleiben.


Richtig konvertieren
In Anwendungen ist es oftmals nötig Datentypen in andere zu überführen. Dabei stellt sich auch oft die Frage was eigentlich am besten/schnellsten ist um einen Datentyp zu ändern. Hier ist deshalb einmal eine kurze Auflistung der Möglichkeiten am Beispiel eines Integers.
Umwandeln von Datentypen
object integerWert = 500;
string stringAlsInteger = "500";

// Wenn man weiss, dass es ein geboxter Integer-Wert ist kann man hart casten..
int wertUnboxed = (int)integerWert;

// ...oder auch weich wenn es sich auf jeden Fall um ein Unboxing handelt
int? wertUnboxedSoft = integerWert as int;

if(!wertUnboxedSoft.HasValue)
{
    // behandeln, wenn Cast fehlgeschlagen
}

// Wenn man weiss, dass es sich um einen String handelt und 100% sicher ist, dass dort ein Integer enthalten ist
int wertParsed = int.Parse(stringAlsInteger);

// Wenn man weiss, dass es sich um einen String handelt, aber nicht weiss ob es sich um einen Integer handelt
int wertMaybeParsed; 
if (!int.TryParse(stringAlsInteger, out wertMaybeParsed))
{
    //behandeln, wenn nicht geparsed werden konnte, ansonsten steht der geparste Wert in "wertMaybeParsed"
}
// Wenn man nicht weiss ob der Integer boxed vorliegt oder als String
// Achtung: Hier kann ggf. InvalidCastException auftreten, die behandelt werden muss
int wertVonIrgendetwas = Convert.ToInt32(integerWert);
Merke:
  • Überlege genau wann was die beste Methode ist um Werte umzuwandeln
  • Wenn möglich sollte auf Convert.To-Methoden verzichtet werden, da sie immer eine Unboxing-Operation durchführen und dessen Ausnahmen gerade in Schleifen ein Performancekiller darstellen.
  •  

C# - 2



    Inhaltsverzeichnis


    Vorwort
    Das vorliegende Dokument soll dafür sorgen, dass der durch das Team produzierte Code einheitlich aussieht. Es ist dabei nicht als Selbstzweck zu sehen, sondern soll dabei helfen, dass sich jeder Entwickler auch in dem Code eines Andere „zu Hause“ fühlt und diesen schnell erfassen und ggf. Fehler korrigieren bzw. die Funktionalität erweitern kann.
    Bei jeder Arbeit am Programmcode sollte sich jeder Entwickler immer wieder vor Augen führen:
           Code is much more read than it is written. - Raymond Chen
    Das gilt natürlich für den Entwickler selbst, der nach einem Jahr in seinem eigenen Sourcecode Erweiterungen vornehmen oder Fehler korrigieren soll. Das gilt aber vor allem auch für andere Entwickler, die mit dem für sie fremden Code in Berührung kommen.
    Grundsätzlich muss also darauf geachtet werden, dass der Code jederzeit von jedem Entwickler in kürzester Zeit erfassbar ist, d. h.
    • sauber strukturiert ist, so dass alle Typen leicht aufgefunden werden können. (Strukturierungsregeln)
    • präzise formuliert ist. Es wurden also sprechende und vor allem treffende Namen verwendet. (-> Namensregeln)
    • sauber formatiert und strukturiert ist, um die lesbar zu erhöhen. (Layoutregeln)
    • ausreichend und treffend kommentiert ist. (Kommentierungsregeln)
    Die in diesem Dokument zusammengefassten Regeln entstammen im Wesentlichen der MSDN Empfehlung.


    Projekteinstellungen
    TODO
     
    Strukturierungsregeln
    Eine Klasse pro Datei
    Für jeden Typ soll eine CS-Datei erstellt werden. Typen sind
    • Klassen
    • Enums
    • Interfaces
    Die Datei soll exakt so heißen wie der Typ selbst. Auf diese Weise ist es leicht möglich, die betreffenden Typen im Projektmappenexplorer zu finden.

    Eine Datei pro Typ
    Es dürfen keine partiellen Klassen verwenden, um eine Klasse auf mehrere Dateien zu verteilen. Dieses Konstrukt ist nur für generierten Code von VS gedacht (und auch notwendig). Sollte die Verteilung auf Grund der Codelänge angedacht sein, sollte eher das Klassendesign überprüft  werden und die Klasse in mehrere Klassen aufgeteilt werden.
    Einzige Ausnahme
    Einzige Ausnahme für partielle Klassen sind die Klassen, die von einem Tool generiert werden (z.B. Telerik, Entitiy Framework, etc.) und dessen Klassen um entsprechende Funktionen erweitert werden sollen. In diesem Falle ist auf der gleichen Ebene wie die generierten Klassen ein Ordner "Extension" anzulegen und die Erweiterungsdateien dort abzulegen. Der Namespacezusatz "Extension" muss dabei zwangsläufig entfallen.


    Namespaces auf Ordner mappen
    Innerhalb eines Projektes sollen die Codedateien anhand Ihres Namesspaces in Ordner aufgeteilt werden, die genauso heißen, wie der Namespace selbst.

    Namensregeln

    Treffende Namen für Variablen
    Variablen sollten so benannt werden, dass klar ist welchen Nutzen sie hat und/oder was die Variable speichert. Auf diese Weise werden Kommentare für Variablen überflüssig und der Code lässt sich schneller erfassen.

    Beispiel
    // --- Dieses Beispiel ist schlecht, weil zwar korrekt kommentiert,
    //     aber der Bezug zur Einheit fehlt
    // Alter in Jahren
    int age = 42;

    // --- Besser:
    int ageInYears = 42;


    Sprache
    Alle fachspezifischen Namen werden in deutscher Sprache verfasst. Allgemeine Begriffe hingegen in Englisch. Beispiel GetBerechtigungsnummerForNutzermedium()
    TODO!
    Möglichkeiten:
    • Deutsch: insbesondere Fachbegriffe sind gut verständlich. Es ensteht aber zwangsläufig eine Mischung mit englischen Ausdrücken (z. B. Get…)
    • Englisch: durchgängig mit allen Befehlen verwendbar. Übersetzung der Fachbegriffe jedoch sehr schwierig. In diesem Fall muss ein Projektweites Wörterbuch geführt werden.


    Keine Abkürzungen verwenden
    Es sollten keine Abkürzungen verwendet werden, da diese zwar für den Verfasser, nicht aber für andere Entwickler verständlich sind. Ausnahmen bilden allgemeingültige Abkürzungen, wie z. B. Id für Identifier und fachlich definierte Abkürzungen wie OrgID für Organisationsidentifikator. 
    Die Verwendung von Akronymen ist erlaubt. Es werden grundsätzlich keine Präfixe für die Kennzeichnung von primitiven Datentypen verwendet. Ausnahmen bilden Controls auf Frontendseiten (z.B. ASP.NET, Winforms).

    Exkurs: Abkürzung vs. Akronym
    Eine Abkürzung ist die verkürzte Schreibweise eines Begriffs, in dem nur eine bestimmte Anzahl aufeinanderfolgender Zeichen genutzt wird. Beispiel: Id für Identifier.
    Ein Akronym ist eine verkürzte Schreibweise für einen zusammengesetzten Begriff, in dem von jedem Begriff der Anfangsbuchstabe verwendet wird. Beispiel: Kfz für Kraftfahrzeug.
    Ausnahmen
    • Interfaces wird ein I vorangestellt (z.B. IRefresh)
    • privaten Variablen, die für Properties verwendet werden, wird ein Unterstrich vorangestellt
    // für den Fall, dass der Getter oder Setter ausprogrammiert werden muss, kann eine private Variable
    // angelegt werden. Name = "_" + <Property-Name>

    private int _myProperty;

    public int MyProperty
    {
        get { return _myProperty; }
        set { _myProperty = value; }
    }

    // Oder Kurzform der Property (der "Normalfall")
    public int MyProperty{ get; set; }

    TODO: Tabelle mit Kürzeln der Controls?

    Besonderheiten bei Namensgebung
    Get vs. Find
    Methode: GetCustomer();  --> Wirft bei Fehlschlag eine Exception (wenn kein Objekt zurückgegeben wird)
    Methode: FindCustomer();  --> Wirft bei Fehlschlag keine Exception, sondern liefert NULL zurück.
    Ein "Getter" impliziert immer, dass man in irgendeiner Form etwas bekommt. Falls dies nicht der Fall ist, ist dies eine Ausnahme, die kenntlich gemacht werden muss. "Find" hingegen bedeutet man versucht etwas zu finden, das Ergebnis ist aber ungewiss.

    Try-Methoden
    Methoden, die das Wort Try beinhalten werfen keine Exceptions, sondern liefern ein Ergebnis über den Returnwert. Dies ist auch die gängige Konvention aus dem .NET Framework
    Beispiel .NET Framework
    //wirft im Fehlerfall eine Exception
    int.Parse(value);

    //liefert true zurück, wenn 'value' erfolgreich geparsed werden konnte
    bool successfullyParsed = int.TryParse(value);


    Events und Delegates
    Die Definition eines Events erfolgt immer über die folgende Konvention:
    Art
    Namenskonvention
    Bemerkung
    Beispiel
    Event
    Beschreibend was das Event macht bzw. liefert
    ein Verb (z.B. Closing) oder Tätigkeit (MessageReceived), die möglichst genau beschreibt
    public event EventHandler<ClosingEventArgs> Closing;
    Eventargumente
    <EVENTNAME> + "EventArgs" ()
    nur wenn eigene Argumente erforderlich, IMMER abgeleitet von System.EventArgs
    public class ClosingEventArgs : ClosingEventArgs
    Eventhandler
    <OBJEKTNAME>_<EVENTNAME>
    Wird von Visual Studio auch so vorgeschlagen
    alte Schreibweise:
    aboutWindow.Closing += new ClosingEventHandler(aboutWindow_Closing);
    ab .NET 2.0
    aboutWindow.Closing += aboutWindow_Closing;
    Event bei Vererbung oder Ausführung
    On + <EVENTNAME>
    Dient ausschließlich der Bereitstellung des Events für abgeleitete Klassen oder Ausführung in der eigenen Klasse.
    protected void OnClosing(ClosingEventArgs e)

    Layoutregeln
    Schreibweisen
    In .Net Programmen gibt es im Prinzip drei unterschiedliche Schreibweisen.
    • CamelCase: Kleiner Anfangsbuchstabe, jedes verbundene Wort wird groß geschrieben. -> private int alterInJahren;
    • lokale Variablen
    • Namen von Parametern für Methoden
    • Felder für Properties mit führendem Unterstrich
    • PascalCase: Großer Anfangsbuchstabe, jedes verbundene Wort wird groß geschrieben. -> public void AbrechnungStarten();
    • Methoden
    • Klassen
    • Properties
    • gängige Abkürzungen
    • Akronyme
    • Alles was sonst nicht unter CamelCase oder UpperCase genannt wird
    • UpperCase: Alle Zeichen werden groß geschrieben.
    • Wenn ein Akronym aus nur zwei Buchstaben besteht, werden beide groß geschrieben, ansonsten keine Verwendung von UpperCase
    • Xml nicht XML
    • Html nicht HTML
    Überflüssige Usings entfernen
    Überflüssige usings müssen entfernt werden. Visual Studio bietet dafür eine Funktion „Sortieren und entfernen“ im Kontextmenü. Zur besseren Lesbarkeit sollten Leerzeilen eingefügt werden, wenn die Hauptnamespaces wechseln.
    usings formatieren
    using System.Collections.Generic;
    using System.Data;
    using System.Diagnostics;

    using Microsoft.CSharp;
    using Microsoft.SqlServer;


    Einrückung von Code
    Grundsätzlich sind zum Einrücken Leerzeichen zu nutzen und keine Tabs. Visual Studio ersetzt in der Grundkonfiguration die Tabs durch Leerzeichen. Diese Einstellung soll beibehalten werden.
    Tipp
    Visual Studio: Tastenkombination STRG + K + D korrigiert die Formatierung des aktuellen Dokuments.


    Geschweifte Klammern
    .Net Konvention ist, dass geschweifte Klammern immer in einer neuen Zeile stehen. Visual Studio setzt diese Konvention automatisch um.
    //gut
    if (c > 6)
    {
        MachWas();
    }

    //schlecht
    if (c > 6) {
        MachWas();
    }

    Leerzeilen
    Leerzeilen sollen überall dort eingesetzt werden, wo ein logischer Bruch besteht, aber keine Klammer steht. Sie werden eingefügt
    • in Using-Blöcken zur Trennung von Namespace-Gruppen
    • zwischen zwei Methoden
    • innerhalb einer Methode zur Strukturierung von Codeabschnitten. Oft wird dieser logische Block mit einem Kommentar ergänzt, der für den gesamten Block gilt.

    Geschweifte Klammer ersetzen Leerzeilen, d. h. dort, wo eine geschweifte Klammer in einer Zeile steht, folgt keine weitere Leerzeile. Es wird immer nur eine Leerzeile eingefügt, nie mehrere nacheinander. Im Gegensatz zu Leerzeichen kann Visual Studio falsche Leerzeilen nicht automatisch korrigieren. Hier ist also Sorgfalt vom Programmierer gefragt. Bei der Codierung müssen unnötige Leerzeilen entfernt werden.


    Leerzeichen
    Mehrfach aufeinander folgende Leerzeichen müssen vermieden werden. Vor und nach runden Klammer werden keine Leerzeichen geschrieben. Nur wenn mehrere Parameter aufeinander folgen wird nach den Komma jeweils ein Leerzeichen eingefügt. Variablendeklaration wird nicht „tabellarisch“ vorgenommen.
    Auch hier sollen die Defaulteinstellungen von Visual Studio genutzt werden.
    //falsch
    int  tag           = 15;
    int  monat         = 10;
    int  jahr          = 2015;
    bool istSchaltjahr = false;

    //richtig
    int tag = 15;
    int monat = 10;
    int jahr = 2015;
    bool istSchaltjahr = false;


    Deklarationen von Variablen
    Es erfolgt immer eine Variablendeklaration je Zeile. Lokale Variablen sind außerdem erst unmittelbar vor der Verwendung zu deklarieren und nicht am Anfang einer Methode.
    //falsch
    int var1, var2, var3 = 42;

    //richtig
    int var1 = 0;
    int var2 = 0;
    int var3 = 42;


    Explizite Angaben von Zugriffsmodifizierern
    Die Sichtbarkeit von Klassen und Methoden muss immer explizit gekennzeichnet sein, um Missverständnissen vorzubeugen. Leider führt Visual Studio die Kennzeichnung nicht automatisch aus.
    //schlecht
    class Test
    {
        int GetValue()
        {
            return 42;
        }
    }

    //gut – muss vom Programmierer eingetragen werden!! VS macht das nicht automatisch!
    public class Test
    {
        private int GetValue()
        {
            return 42;
        }
    }


    Anweisungen
    Die Regeln sind für alle Anweisungen (if, while, foreach usw.) gleich: In den runden Klammern werden keine Leerzeichen benutzt, auch dann nicht, wenn sich nichts darin befindet. Geschweifte Klammern werden zwecks besserer Lesbarkeit und Wartbarkeit immer benutzt, auch wenn der jeweilige Block nur aus einer Anweisung besteht.
    // falsch
    if (op1 > 1)
       op1--;
    else
       op1 = 0;

    // richtig
    if (op1 > 1)
    {
       op1--;
    }
    else
    {
       op1 = 0;
    }


    Kommentierungsregeln
    Hinweis
    Beim Kommentieren von Programmcode sollte man sich immer folgendes vor Augen halten: Eine von IBM durchgeführte, sechsmonatige Studie ergab, dass Wartungsprogrammierer die meisten Schwierigkeiten dabei hatten, die Absicht des Autors zu verstehen.


    Typen von Kommentaren
    Kommentare sollen helfen, sich einen schnellen Überblick über den Code zu verschaffen und möglichst schnell die gesuchte Codestelle zu finden, in die man ggf. eingreifen kann. Sehr viel trägt dazu natürlich lesbarer Programmcode bei, der aus der Anwendung der in den vorangegangenen Abschnitten festgelegten Regeln entsteht.
    Für die Orientierung ist aber ein zusammenfassender Kommentar erforderlich, damit nicht der gesamte Programmcode gelesen und verstanden werden muss, obwohl er u. U. gar nicht relevant ist. Der Leser soll erkennen können, was der Programmierer mit dem Code bezweckt. Dazu sind insbesondere der „XML-Kommentar für öffentliche Elemente“ und die „Inline-Kommentare“ wichtig. Darüber hinaus sollten gerade bei der Wartung des Codes „Änderungsmarkierungen“ gesetzt werden, die auf den Grund der Änderung hinweisen und auf die Quelle verweisen.
    Zu vermeiden sind Kommentare, die
    • den Programmcode wiederholen
    • den Programmcode erklären
    In letzterem Falle ist der Code offenbar so verwirrend, dass der Programmierer selbst bemerkt hat, dass er ihn erklären muss, damit ihn anderen verstehen können. In diesem Fall sollte eher über eine Codeverbesserung nachgedacht werden.
    Schlechter Kommentar -> Wiederholen von Programmcode
    //schlecht
    private int Summe(int op1, int op2)
    {
        // Parameter addieren und lokaler Variable zuweisen
        int sum = op1 + op2;
        return sum;
    }


    Inline-Kommentare
    • Kommentare gehören immer über die zu kommentierende Zeile.

    Inline-Kommentare
    //falsch
    int age = 42; // Alter in Jahren

    //richtig
    //Alter in Jahren
    int age = 42;

    //noch besser, da der Kommentar überflüssig ist
    int ageInYears = 42;

    • In .Net werden keine Blockkommentare mehr verwenden, auch wenn diese syntaktisch noch zur Verfügung stehen (stammen aus C++)
    Blockkommentare
    /* falsch
     * dieses ist ein Kommentar für die Funktion GetValue,
     * der sich über mehrere Zeilen erstreckt.
     * Der Blockkommentar wird nicht mehr verwendet*/
    private int GetValue()
    {
        return 42;
    }

    // richtig
    // In .Net wird jede Zeile einzeln mit den Kommentar-
    // kennzeichen versehen. Zum Auskommentieren eines
    // Blocks bietet VS eine Funktion in der Toolbar.
    private int GetValue()
    {
        return 42;
    }
    XML-Kommentar für öffentliche Elemente
    Für alle öffentlichen Elemente (Methoden, Klassen usw.) werden XML Kommentare verwendet, die auch Tags für bestimmte Elemente bieten. Aus diesen Kommentaren kann eine Dokumentation generiert werden.
    Kommentare für Methoden beginnen mit einem Verb. Gedanklich kann zur Formulierung ein „die Methode…“ ergänzt werden, welches aber nicht geschrieben wird. Für die Dokumentation der Parameter gilt das gleiche. Das „returns“ ist bereits ein Verb, daher bildet dieses Attribut eine Ausnahme. Visual Studio fügt diese Kommentare automatisch ein, wenn über ein öffentliches Element der XML-Kommentarzeichen "///" geschrieben wird.
    XML-Kommentare
    /// <summary>
    /// addiert zwei Operanden
    /// </summary>
    /// <param name="op1">repräsentiert den ersten Operanden</param>
    /// <param name="op2">repräsentiert den zweiten Operanden</param>
    /// <returns>die addierten Operanden</returns>
    public int addValues(int op1, int op2)
    {
        return op1 + op2;
    }

    Damit die Kommentare sinnvoll eingesetzt werden können, ist auf eine korrekte Rechtschreibung und Interpunktion zu achten. Die Formulierung ist so zu wählen, dass man sie auch einem Kunden zeigen kann. Sie enthält also keine Unmutsäußerungen oder ähnliches.


    Unfertiger Code und Hacks

    Visual Studio bietet einige Tags, die Code gesetzt werden können, damit gewisse Stellen leicht wiedererkannt werden können. Wird ein solches Tag in einen Kommentar geschrieben, erscheint dieser Kommentar in der Visual Studio Aufgabenliste.
    private int Bla()
    {
        //TODO: MH 27.12.14: hier fehlt noch die Berechnung
        return 42;
    }
     private float KomplizierteBerechnung()
    {
        float erg = CalcSpecialFactor() * GetPrice();

        //HACK: MH 27.12.14: CalcSpecialFactor liefert noch falsches Ergebnis, vorübergehend als 1 annehmen
        return GetPrice();
    }

    Diese Tags können in Visual Studio konfiguriert werden unter TOOLS / OPTIONEN und dort unter Umgebung / Aufgabenliste

     
    Änderungsmarkierungen
    Durchgeführte Änderungen am Programmcode sollten im TFS dokumentiert werden, damit ein geändertes Programmverhalten nachvollziehbar bleibt.
    TODO: Definieren wie dies zutun ist

    Weitere Programmierregeln
    Konstruktoren immer explizit angeben
    In C# werden standardmäßig leere Konstruktoren erstellt, sollte die Klasse keinen eigenen Konstruktor haben. Wird nun ein neuer, zusätzlicher Konstruktor erstellt, so nimmt C# diesen als Standardkonstruktor an und erstellt keinen leeren mehr. Dies erzeugt überall, wo sonst der leere Konstruktor verwendet wurde, einen "breaking-change", der dafür sorgt, dass man den Code anpassen muss. In neuen Klassen muss deswegen, wenn kein spezieller Konstruktor verwendet werden soll, der leere Konstruktor explizit angegeben werden. Auf diese Weise beugt man diesem Fehler vor.

    Validierung von Eingaben
    Bei der Codierung ist immer davon auszugehen, dass der Benutzer Eingaben tätigt, die so vom System nicht erwartet wurden. Daher wird jede Benutzereingabe validiert, bevor sie weiter verarbeitet wird.

    Prüfen von Parametern
    Bei Methoden, die als public definiert sind, erfolgt eine Validierung der übergebenen Parameter. Der Entwickler geht immer davon aus, dass übergebene Parameter nicht sinnvoll oder unvollständig sein können. Parameter müssen daher auf null, zulässige Wertebereiche, unerwartete Inhalte u. ä. geprüft werden, bevor sie weiter verwendet werden. Die Parameterprüfung löst eine Exception aus, wenn die Validierung nicht erfolgreich war.

    Hardcodierte Werte
    Keine Zahlen ("magic numbers"), Konfigurationsparameter (z. B. Pfade) oder sonstige konstante Werte im Code. Diese werden immer aus Konfigurationsdateien / Systemtabellen gelesen oder als Konstanten im Code definiert (z.B. als Enums).

    Methodenausstieg
    Zur besseren Übersicht besitzen Methoden nach Möglichkeit nur einen einzigen Ausstiegspunkt (return). Mehrere return-Kommandos in einer Methode sind allerdings im Zuge von Ausnahmebehandlungen möglich. Weiterhin wird der Rückgabewert einer Methode wird nicht zur Fehler-Anzeige mißbraucht. Es darf z.B. kein null-Wert zurückgegeben, um der aufrufenden Methode mitzuteilen, dass die Methode nicht fehlerfrei durchgeführt werden konnte. Für solche Fehlermeldung werden Ausnahmen verwendet, die von der aufrufenden Methode in entsprechender Weise aufgefangen und behandelt werden müssen.

    Zugriff auf Membervariablen
    Membervariablen werden immer private, bzw. protected deklariert. Ein Zugriff von Außen erfolgt immer über public Properties (get/set). Klassen-Konstanten dürfen public sein.

    Todo-List
    • IDisposeable beschreiben
    • Technische Dokumentation im Projekt verwalten
    • Returns vs. Exceptions
    • Umgang mit TFS
    • UnitTest
    • Ablauf: wann wird eingecheckt, wann werden Testmethoden erstellt, wann wird dokumentiert,   wann wird der Arbeitspunkt aktualisiert.