Programming with C#

Top1 Links und Referenzen

Top2 Wesentliche Unterschiede zu C++

  • Grundlegende Zielsetzungen
    • C++ ist eine allgemeine objektorientierte Sprache, ursprünglich entwickelt für Standalone-Rechner mit Kommandozeilen-basiertem UserInterface, wichtige Basisbiliothek: Standard library
    • C# wurde für .NET entwickelt, unterstützt grafisches UserInterface (Windows) / Netzwerk / Internet (Web Services), verwendet die .NET base class library
      "In the commercial world of programming: being easy to understand is usually worth more than being artistic" (keine stream Operatoren, keine Templates)
  • Compile target
    • C++ wird zu Assembly-Code compiliert
    • C# wird zu intermediate language (IL) compiliert. IL wird erste auf dem Zielrechner durch Just-In-Time (JIT) Compilierung zu nativem ausführbarem Code. IL-Code wird zusammen mit Metadaten innerhalb einer Assembly gespeichert (entspricht einer vom C++-Compiler generierten DLL oder EXE)
  • Explizite Referenzen
    • C++ erfordert #include-Statements (Compiler) und Library-Angaben (Linker)
    • C# separiert nicht zwischen Compilen und Linken, alle Class-Definitionen (aus der base class library) werden automatisch ohne weitere Referenzierung im Quellcode gefunden
  • Memory management
    Der Garbage Collector übernimmt automatisch die Freigabe von auf dem Heap allokierten Speicher, sobald er nicht mehr benötigt wird.
  • Destructors
    Es ist nicht garantiert, wann ein Klassendestructor vom Garbage Collector aufgerufen wird. Es gibt kein "delete". Im Klassendestruktor sollten daher meist keine spezifischen Anweisungen enthalten sein. Zum gezielten Freigeben von Ressourcen (z.B. Datenbank, File) muss das IDisposable interface implementiert werden.
  • Zeiger / Pointer
    werden normalerweise nicht benötigt, stattdessen werden Referenzen eingesetzt
  • class / struct
    • class: reference type (heap), normalerweise größeres Objekt mit vielen Methoden, unterstützt einfache Vererbung, mehrfache Vererbung von Interfacecs
    • struct: value type (stack), normalerweise kleines Objekt mit einfacher Sammlung von Variablen, unterstützt keine Vererbung, keine virtuellen oder abstrakten Methoden, Default-Konstruktor wird immer vom Compiler generiert und kann nicht ersetzt werden
    • alle Funktionen und Variablen müssen Bestandteil einer class oder eines structs sein, sie dürfen keine Top-Level Items sein
  • Exceptions
    zusätzliche Unterstützung für finally blocks
  • neue Konzepte
    • Delegate: verkapselt Methodenaufruf und zugehörige Objektreferenz
    • Events: ähnlich Delegate, wird eingesetzt für Notifikationen/Callbacks
    • Properties: entspricht einem get/set Methodenpaar, Syntax entspricht direktem Feld/Attributzugriff
    • Interfaces: Menge von Methoden und Properties, die eine Klasse implementieren kann
    • Attribute: Klassen, Methoden, Parameter können mit Meta-Informationen ausgezeichnet werden, auch die Definition eigener Attribute ist möglich. Durch Attribute kann z.B. die Compilierung beeinflusst oder zusätzliche Dokumentation generiert werden.
      [Conditional("Debug")] // method will only be available in Debug version
      void DoSomeDump(){...}
      
    • Reflection (.NET base classes): Infos über die Definitionen von Klassen innerhalb von Assemblies können dynamisch ermittelt werden
  • KEINE Unterstützung für
    • Mehrfachvererbung: nur für Interfaces möglich
    • Templates
    • Headerfiles
      es gibt keine Trennung zwischen Deklaration und Definition/Implementierung, es gibt keine Headerfiles, alle Methoden müssen direkt innerhalb der Klassendefinition implementiert werden.
      Übersicht wird trotzdem gewährleistet:
      • moderne Editoren erlauben ein "Collapse" der Implementation-Bodys
      • es gibt eine Facility zur automatischen Generierung von Quellcode-Dokumentation im XML-Format
    • Forward declaration: nicht notwendig, da Symbole auch ohne vorausgehende Deklaration verwendet werden können
    • Default parameter values: durch überladene Methoden realisierbar
  • Scope resolution
    ClassName.Membername (statt wie in C++ ClassName::MemberName)
  • die wichtigsten Preprocesor-Direktiven (z.B. #define, #if, #else) sind verfügbar

Top3 Elementarbeispiel

// not necessary: includes to use library functions/classes

using System;
using System.Windows.Forms;

namespace MyConsole // all items "should" be within a namespace
{
    class MyClass
    {
        // entry point, each function "must" be a member of a class or struct
        static int Main (string[] in_commandLineArgs)
        {
            Console.WriteLine ("Hello, World!");
            MessageBox.Show("Hello, World!");
            return 0;
        }
    }
}


Top4 Classes

Top4.1 Klassendefinition

class MyClass : MyBaseClass
{
    // Constructor
    MyClass (int in_num)
    : base (in_num) // executes the base class constructor
    {...}

    // Constructor
    MyClass (double in_num)
    : this () // executes the 0 param MyClass constructor
    {...}

    // Default constructor (replaces compiler generated version)
    // "private" prevents instantiation from outside
    private MyClass(){...}
    
    // static constructor
    static MyClass()
    {
        // init static fields
    }
    
    // Any method
    public int DoSomething()
    {
        return 17;
    }

    // operator overloads always are static methods
    public static MyClass operator + (MyClass lhs, MyClass rhs)
    {
        MyClass ret = new MyClass();
        ret.m_strField = lhs.m_strField + rhs.m_strField;
        ret.m_intField = lhs.m_intField + rhs.m_intField;
        return ret;
    }
    
    // regular fields with direct initialization
    private string m_strField = "Initial value";
    private int    m_intField = 36;
    
    // static fields with initialization
    private static double s_doubleField = 3.14;
    
    // constant
    // set at compile time, cannot be changed within constructor,
    // is implicitly static
    public const int PI = 3.14;
    
    // readonly constant
    // initialized once at runtime within a static constructor
    public static readonly int EPSILON;
}
  • Vererbung ist immer public
  • als Default wird immer System.Object als Basisklasse (oder einfach "object") angenommen, daraus steht insbesondere die Methode ToString() zur Verfügung
  • jedes Member hat einen expliziten Access Modifier
  • zusätzlich zu private, protected, public gibt es noch
    • internal: Zugriff durch Code in der selben Assembly
    • protected internal: Zugriff durch abgeleitete Klassen in der selben Assembly
  • inline gibt es nicht: Der JIT-Compiler weiss ohne Hilfe durch den Entwickler, wie er den IL-Code optimieren kann
  • Die Methoden-Implementierung kann nicht ausserhalb der Klassendefinition ergänzt werden
  • In der Constructor-Initializer-Liste kann nur der Basisklassen-Construcor ("base(...)") oder ein anderer Konstruktor der Klasse ("this(...)") aufgerufen werden. Datenmember werden direkt in der Klassendefinition initialisiert.
  • Über einen statischen Konstruktor können die statischen Variablen initialisiert werden (z.B. mit Werten aus einer Datenbank), Aufruf wird von der .NET Runtime beim Laden der Klassendefinition ausgeführt.

Top4.2 Memory Management und Destruktoren

  • die meisten Klassen benötigen keinen Destruktor, da der GarbageCollector (GC) das Freigeben des Memorys regelt
  • es kann nicht vorhergesagt werden, wann der Aufruf des Destruktors erfolgt
  • zum gezielten Freigeben von Ressourcen (Files, Datenbanken):
    • Ableitung von Interface IDisposable
    • expliziter Aufruf der Methode MyClass.Dispose(), sobald das Objekt nicht mehr benötigt wird.
    • sicherheitshalber sollten auch noch im Destruktor die Ressourcen abgebaut werden (falls der Aufruf von Dispose vergessen wird)
// within class, implementation of Dispose:

public void Dispose()
{
    Dispose(true);

    // inform Garbage Collector that a call of the destructor
    // is no longer necessary and the freeing of managed memory
    // need not be delayed to long
    GC.SuppressFinalize(this);
}

protected virtual void Dispose (bool disposing)
{
    if (disposing)
    {
        // cleanup of managed resources
    }
    // cleanup of unmanaged resources
}

~Destructor()
{
    // cleanup resources
}
Der Aufruf von Dispose() kann auch automatisch ausgeführt werden
using (MyClass myObject = new MyClass())
{
    // use myObejct here

} // myObejct.Dispose() will be called automatically here

Top4.3 Virtual, Override

Eine Klassenreferenz kann sich auf eine Basisklasse oder irgendeine abgeleitete Klasse beziehen.
MyBaseClass myBaseObject;
myBaseObject = new MyDerivedClass();

// each class derives from .NET library class System.Object, which
// is represented as "object" within C# syntax
object myObject = new MyClass();
Überschreiben virtueller und nicht virtueller Methoden:
class MyBaseClass
{
    public virtual void DoSomethingVirtual(int in_num) {...}
    public void DoSomethingNonVirtual(int in_num) {...}
    
    // a derived class can only be instantiated if all abstract
    // methdods have been implemented
    public abstract void DoAnythingElse();
}

// Override a virtual and a non virtual function
class MyDerivedClass : MyBaseClass
{
    public override void DoSomethingVirtual(int in_num) {...}
    public new void DoSomethingNonVirtual(int in_num) {...}
}

Top4.4 Userdefined casts, cast Operationen

Die cast-Methode muss eine statische Methode der Source- oder Destination-Klasse sein:
// cast can be defined as implicit or explicit
public static implicit operator MyDest (MySource in_source)
{
    ...
    // return MyDest instance
}
Anwendung:
MySource source = new MySource();

// if cast operator was defined "implicit"
MyDest dest = source;

// if cast operator was defined "explicit"
MyDest dest = (MyDest) source;
Reaktion bei Fehlschlag der cast-Operation von Basisklasse auf abgeleitete Klasse:
// throws an exception
MyDerivedClass derived  = (MyDerivedClass) baseRef;

// use null instead of exception
MyDerivedClass derived  as (MyDerivedClass) baseRef;
// derived now has value "null"

Top4.5 Interfaces

Definition eines Interfaces:
interface IMyInterface
{
    void DoSomething (int in_num);
}

// not allowed: access modifiers, implementation code, virtual, abstract
// deriving from other interface is possible
Implementierung von Interfaces inerhalb einer Klasse:
class MyClass : MyBaseClass, IMyInterface, IMyInterface2
{
    // implement interface method
    // here we choose to define it as "public virtual"
    public virtual void DoSomething (int in_num){...}
    
}
Prüfung, auf Vorhandensein eines Interfaces zur Laufzeit
IMyInterface myInterface;
myInterface = someObject as IMyInterface;
if (myInterface != null)
{
    // interface exists and can be used
}

Top5 Besondere Syntax-Erweiterungen

Top5.1 Condition expressions vom Typ bool (z.B. in if, while)

  • müssen vom Typ bool sein, eine implizite Prüfung auf 0 ist nicht erlaubt
  • d.h. statt wie in C++: if (myInt) in C# zwingend: if (myInt != 0)

Top5.2 switch (string-Testvariable, goto)

  • Prüfung auf string ist möglich
  • "fall through" über "goto"
  • Beispiel
    switch (myString)
    {
        case "SomeText":
            // do something
            break;
    
        case "AnotherText";
            // do something
            goto case "NextCase"; // no implicit fall through allowed,
                                  // if case is non empty
            
        case "NextCase": // implicit fall through is allowed 
                         // for empty case
        
        case "LastCase":
            // do something
            break;
    
        default:
            // do something
            break;
    }
    

Top5.3 foreach (Iteration in array oder collection)

foreach (int number in myNumberArray)
{
    Console.WriteLine (number); // access to elements is READONLY
}
Zum Verändern der Elemente ist eine herkömmliche for-Schleife erforderlich:
for  (int i = 0; i< myNumberArray.Length; i++)
{
    myNumberArray[i] += 3; // change element
}

Top5.4 Datentypen

Basic Datatypes
können auch als Objekte behandelt werden. Dadurch stehen je nach Typ folgende Methoden zur Verfügung:
  • ToString (auch 4711.ToString())
  • Parse (double d = double.Parse("3.14"))
  • MinValue, MaxValue
  • Epsilon
  • NaN (not a number, undefinierter Wert)
  • für char: IsLetter, IsNumber, ToUpper, ToLower
Typ string
  • "\0" ist kein Endezeichen, sondern ein zulässiges Zeichen innerhalb des Strings
  • "@" vor einen String gestellt deaktiviert das Escaping, d.h. es ist möglich zu schreiben:
    string myFilePath = @"C:\MyFolder\MySubFolder"; // double "\\" is not required
    
    string multiLineMessage = @"Line 1
    Line2
    Line3"; // carriage returns are part of the defined string
    
    
Explizite casts (myShort = (long) myLong)
sind notwendig, wenn möglicherweise Datenverlust auftreten kann. Eine Absicherung ist über einen "checked"-Block möglich, der eine Exception wirft, wenn die .NET runtime einen overflow feststellt.

Top5.5 Reference and value types, new operator, Boxing

Folgende Datentypen sind immer reference types: class, string, object
Alle anderen Typen sind value types: einfache basic types, structs, enumerations

reference type

MyClass var1;         // uninitialized reference

var1 = new MyClass(); // new calls default constructor
                      // initializes fields,
                      // and allocates memory on the heap

var1.SomeIntField = 17;

MyClass var2 = var1;          // var2 now points to the same object as var1
var2.SomeIntField = 36;       // also changes var1.SomeIntField

var2 = null;                  // var2 does no longer refer to anything
                              // var1 still refers to the object
value type
MyStruct myVar; // creates struct on the stack,
                // but does not call constructor,
                // fields are uninitialized
                
myVar = new MyStruct(); // new executes default constructor,
                        // initializes fields,
                        // no effect on memory allocation,
                        // already exists on stack
Boxing: Bei Bedarf kann auch ein value type in einen reference type umgewandelt werden:
int i = 33;

object boxedI = (object) i;
// now use the reference e.g. by calling a method expecting a reference

// somewhere else convert back to value type:
int k = (int) boxedI; // may throw an exception

Top5.6 Initialisierung von Variablen

Member fields werden mit der Ausführung des Constructors automatisch ausgenullt, d.h. numerische Werte haben Wert "0", boolsche Werte sind "false", Referenzen sind "null".

Lokale Variablen müssen explizit initialisiert werden:

int myInteger;         // will contain random data
myInteger = new int(); // now default constructor initializes with 0

// or simpler:
int anotherInt = 0;

Top5.7 Properties

class MyClass
{
    private int m_someInt;
    
    // define property "SomeInt"
    // Remark: Omitting set/get is possible and leads to
    // a read-only / write-only property
    public int SomeInt
    {
        get
        {
            return m_someInt;
        }
        
        // implicit input param is "value", return type is void
        set
        {
            m_someInt = value;
        }
    }
}

Top5.8 Assignment operator (=)

  • einfache Datentypen werden kopiert
  • struct: "shallow copy", d.h. wie in C++ ein direktes Memcopy der struct-Daten
  • class: es wird die Referenz (Memory-Adrersse) kopiert und nicht die Daten!
    sollen Daten kopiert werden, muss die Methode MemberwiseCopy() überschrieben werden

Top5.9 Reference parameter

Damit ein an eine Methode übergebener Parameter geändert werden kann, muss der Parameter als Referenz übergeben werden.

Top5.9.1 InOut-Params

// definition of method within MyClass
public void ChangeNumbers (ref int io_numberOne, ref int io_numberTwo);

...
// Calling method ChangeNumbers requires initialization
// of ref params and again usage of attribute "ref"
int numberOne = 12;
int numberTwo = 400;
myClass.ChangeNumbers (ref numberOne, ref numberTwo);

Top5.9.2 Out-Params

Erfordert die Methode nicht die Übergabe eines gültigen Eingangswertes, so kann der Parameter mit dem Attribut "out" definiert werden. Eine Initialisierung vor Aufruf ist dann nicht notwendig.
// definition of method within MyClass
public void CalculateResult (int in_startValue, out int out_result);

...
//Calling method CalculateResult does not
// require initialization of out param, but again usage of attribute "out"
int result; // not initialized
myClass.CalculateResult (4711, out result);

Top5.10 Arrays

// initialized with default constructor, here 0.0,
// the size of the array belongs to the instance not to its type
double [] myArray1 = new double[10];
double [] myArray2 = new double[50];

// explicit initialization
double [] myArray3 = {1.0, 2.0, 3.0, 4.0, 5.0,
                      6.0, 7.0, 8.0, 9,0, 10.0};
                      
// example for three dimensional rectangular array
// number of elements is 2x3x2 = 12
int [,,] myArray3D;
myArray3D = new int [2,3,2];

// example for two dimensional jagged array
// number of elements can be specific for each row
int [][] myJaggedArray = new int [3][];
for (int = 0; i<3; i++)
    myJaggedArray[i] = new int [2*i];
  • Arrays sind Objekte vom Typ System.Array, die auf dem Heap gespeichert sind und vom Garbage Collector verwaltet werden
  • Anzahl der Elemente über Property Length
  • automatische Überprüfung der Indexgrenzen, bei Bereichsüberschreitung wird eine IndexOutOfBounds Exception geworfen
  • die Arraygröße muss bereits zur Compile-Zeit angegeben werden, Veränderungen zur Laufzeit sind z.B. für Objekte vom Typ System.Collections.ArrayList möglich

Top5.11 Enumerations

// definition of enum
enum Color {RED, GREEN, BLUE, YELLOW}

// usage requires the enumeration name:
Color myColor = Color.GREEN;

// read value from string
Color myColor = (Color) Enum.Parse (typeof(Color), "YELLOW", true);
  • ein enum entspricht einem struct, das von System.Enum abgeleitet ist
  • myColor.ToString() liefert z.B. "GREEN"
  • Parse() erlaubt das Einlesen von Enum-Werten aus einem String

Top5.12 Exceptions

try
{
    // do something which may cause an exception
    
    // code may contain several calls of return:
    // before really returning the finally block is executed!
    return;
}

// optionally catch the exception to perform
// some error handling
catch (MyException e) // MyException must be derived from System.Exception
{
    // error handling
}

// optionally perform some cleanup operations
// block is also called without preceding catch block
finally
{
    // code is executed in any case before method returns
    // e.g. free resources allocated within try block
}

Top5.13 Pointers

Pointer können innerhalb unsafe code blocks verwendet werden:
  • unsafe method: public unsafe void SomeMethod(){...}
  • unsafe class: unsafe class SomeClass(){...}
  • unsafe block: unsafe {...}
Einschränkungen:
  • nicht möglich: Dereferenzierung von void-Pointern, Pointer-Arithmetik, Zeiger auf Referenzobjekte
  • notwendig: Compiler-Option /unsafe
  • Zeiger auf valuetype Objekte innerhalb einer Klasse dürfen nur inerhalb eines fixed-Blocks verwendet werden. Dadurch wird der GarbageCollector davon abgehalten, das Objekt im Speicher zu verschieben:
    MyClass myObject = new MyClass();
    fixed (int *pInt = &myObject.m_someIntField)
    {
        // use pointer to m_someIntField;
    }
    

Top5.14 Delegates

Ein Delegate entspricht einem Funktionszeiger kombiniert mit einem Zeiger auf das zugehörige Objekt. Ein Delegate ist eine Klasse, abgeleitet von System.Delegate.

Definition eines Delegate:

delegate void MyDelegateFunction (int in_num);
Beispielklasse, deren Methode über das Delegate aufgerufen werden soll:
class MyClass
{
    void MyMethod (int in_maxNum){...};
}
Beispielmethode, die einen Delegate als Parameter erwartet
void MethodAcceptingDelegate (MyDelegateFunction in_function)
{
    // call the method with param 36
    in_function(36);
}
Einsatz des Delegate:
MyClass myObject = new MyClass;
MyDelegateFunction myFunc = new MyDelegateFunction (myObject.MyMethod)

// simply call delegatefucntion
myFunc(4711);

// pass it to some other method
MethodAcceptingDelegate(myFunc);
Delegates können mehrere Methoden verkapseln (Verknüpfung mit +=), die dann nacheinander aufgerufen werden.

Top5.15 Events

Events sind eine Spezialform von Delegates mit folgender Signatur:
delegate void MyEventClass(obj sender, EventArgs e);

Deklaration eines Events innerhalb einer Klasse:
public event MyEventClass OnMyEvent;

Verknüpfung des Events mit einer Handlerroutine:
eventSourceObject.OnMyEvent += MyHandlerMethod;

Auslösen des Events in der EventSource-Klasse:
OnMyEvent(this, new EventArgs());

Top5.15.1 Beispiel für eine Event-auslösende Klasse

// Definition of custom EventData:
// Notification data delivered together with the PerformanceDataEvent
public class PerformanceDataEventArgs : EventArgs
{
    private readonly double m_counterValue;
    ... // other data and accessor functions
}

// Delegate declaration
// Clients may implement handler methods with this signature:

// The current counter value is delivered
public delegate void EventHandler_PerformanceData(object sender,
    PerformanceDataEventArgs e);


// Event source
// Class PerformanceTracker watches an arbitrary system performance counter
// and emits events with the current counter values.
class PerformanceTracker
{
    //-----  Event  -----

    // The current counter value is delivered
    public event EventHandler_PerformanceData
        Event_PerformanceData;

    //-----  Internal helper method to create and send event  -----

    protected virtual void RaiseEvent_PerformanceData()
    {
        EventHandler_PerformanceData handler = Event_PerformanceData;
        if (handler != null) // there are really clients registered
        {
            // store performance data within specific EventArgs
            PerformanceDataEventArgs e = new PerformanceDataEventArgs(
                m_info, m_currentCounterValue);

            // Invokes the delegates, i.e. informs all registered clients
            handler(this, e);
        }
    }

    //-----  Application code raising event via helper function  -----

    // Periodic timer handler method
    private void RetrievePerformanceData()
    {
        ... // get performance data and store in class atribute

        // Inform clients about new counter value
        RaiseEvent_PerformanceData();
    }
}

Top5.15.2 Beispiel für eine Event-empfangende Klasse

// Class which is interested on performance data
// received from class PerformanceTracker
class SomeClient
{
    SomeClient()
    {
        m_performanceTracker = new PerformanceTracker();

        // Connect with events from PerformanceTracker
        m_performanceTracker.Event_PerformanceData +=
            this.NewPerformanceDataReceived;

        // Start tracking
        m_performanceTracker.StartTracking(m_perfCounterInfo);

        // From now on we will receive all performance data through
        // event handler routine NewPerformanceDataReceived
    }

    // Event handler routine to process performance data
    public void NewPerformanceDataReceived(object sender, PerformanceDataEventArgs e)
    {
        // display the received event data somewhere on the GUI
        lblCurrentCounterValue.Text = e.m_counterValue.ToString();
    }
}

Top6 GUI-Programming

Top6.1 Menus

  • in der Entwurfsansicht einer "Form" im "Menubereich" einfach neuen Menu-Punkt eingeben
  • im Fenster "Property"/"Eigenschaften":
    • Name für das MenuItem vergeben, z.B. "menu_file_save" (dieser Name wird dann für die Handler-Routine verwendet)
    • optional: "ShortcutKeys" und "ShortcutKeyDisplayString" ergänzen
  • Doppelklick auf MenuItem erzeugt Handlerroutine