C# Basics Teil 3

Der dritte Teil handelt von den Preprozessor Direktiven, die von Leuten mit C/C++ Erfahrung ohne weiteres übersprungen werden können. Interessanter wird es bei den Iteratoren.

Preprozessor Direktiven

Die Preprozessor Direktiven sind alten C/C++ Hacker längstens bekannt. Es ist auch ein offenes Geheimnis, dass man damit allerlei Unfug im Code anstellen kann. Es sind schlichte Anweisungen an den Preprozessor, gewisse Dinge vor der Komplation ein-/abzuschalten. Sie werden mit dem Prefix # gekennzeichnet. Eine Direktive die sehr grosse Bekanntheit geniesst, ist das Paar #region / #endregion. Diese werden jedoch nur zur Gliederung verwendet.

Beispiel:

#define MYSYMBOL

#if DEBUG
public void Foo() { }
#elif MYSYMBOL
public void Foo(int a) { }
#else
public void Foo2() { }
#endif

In diesem Beispiel werden die Direktiven #define, #if, #elif und #endif dafür verwendet, eine Methode Foo() je nach dem in den Code zu kompilieren oder nicht. Im Debug-Modus gibt es die Methode Foo(). Wird bevor der Preprozessor diesen Code prozessiert ein #define MYSYMBOL gesetzt (und mit #undefine MYSYMBOL nicht wieder rückgängig gemacht) und nicht im Debug-Modus kompiliert, kann auf die Foo(int a) Methode zugegriffen werden. Im Else-Fall (z.B. Release-Modus) ohne definiertes MYSYMBOL gibt es nur die Foo2() Methode. Bereits bei dieser Beschreibung sollte es Auffallen, dass diese Direktiven nur in wenigen Situationen benutzt werden sollten. Um den Programm-Code zu beschleunigen, könnte man z.B. alle Ausgaben in einer Console nur in Debug-Kompilationen ermöglichen.

Pragma Direktiven

Die Pgragmas werden verwendet, um dem Kompiler zusätzliche Anweisungen zur Kompilation an einer bestimmten Stelle zu geben. Es gibt ein pragma, um Warnungen ein-/ausgeschalten zu können. Ein weiteres Pragma kann benutzt werden, um die checksum der Datei ins Debug-File (*.PDB) zu schreiben. Das #pragma pack(…) von C/C++ bekannt, um Typen, Strukturen das Verhalten im Stack zu manipulieren, wurde bis zum jetztigen Zeitpunkt zum Glück nicht implementiert.

Beispiel:

#pragma warning disable 414
static string Message = „Hello“;
#pragma warning restore 414

Dieses Beispiel schaltet für eine Programmzeile die Warnung „…Message is assigned but its value is never used“ aus.

Was es sonst noch für Direktiven gibt, kann in der MSDN nachgelesen werden.

Die Semantik von Iteratoren

Ein Iterator wird benutzt, um eine Menge von Elementen mit einer Schleife zu durchlaufen. Um eine eigene Klasse mit einer Iterationsmöglichkeit zu bestücken, muss IEnumerable<T> oder IEnumerator<T> implementiert werden. Die jeweiligen nicht generischen Interface-Brüder IEnumerable und IEnumerator müssen auch implementiert werden, weil die generischen Schnittstellen sich davon ableiten. Ein grosser Vorteil von Iteratoren ist die Kapselung der Logik in eine andere Klasse. Zudem kann jede Klasse die das IEnumerable<int> implementiert für foreach-Schleifen benutzt werden. Das Konstrukt gibt uns zusätzlich die Möglichkeit, die internen Werte eines privates Feld nach aussen zugänglich zu machen, ohne die Sichtbarkeit des Feldes selber zu verändern.

Beispiel:

public class Foo : IEnumerable<int>
{
// Internes Feld
private int[] _dump = new int[] { 1, 2, 3 };

// Implementation der generischen Schnittstelle
public IEnumerator<int> GetEnumerator()
{
for (int i = 0; i < _dump.Length; i++)
yield return _dump[i];
}

// Explizite Implementation der „alten“ Schnittstelle.
// Mit einer einfachen Weiterleitung ist dies bereits erledigt.

IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}

Man beachte das Keyword yield return. Es ist ein ganz spezielles Konstrukt. Der Kompiler merkt sich die Position des Programm Counters, wenn er mit yield return die Methode verlässt. Beim nächsten Aufruf springt er direkt wieder an die zuvor verlassene Stelle und setzt dort das Programm fort. Mit dieser Implementation kann man nun in diversen Arten über die Elemente im dump iterieren:

Foo f = new Foo();

// iteration mit foreach
foreach (int i in f) Console.Write(i);

// iteration mit einem enumerator
IEnumerator<int> e = f.GetEnumerator();
while (e.MoveNext()) Console.Write(e.Current);

// iteration mit linq
var v = from n in f select n;
foreach (int i in v) Console.Write(i);

Der eigene Enumerator

Möchte man nun einen eigenen Enumerator schreiben, um die Logik von der Haupklasse auszulagern, muss man das IEnumerator<T> implementieren. Natürlich kann man nun yield return nicht mehr verwenden und muss einen eigenen index führen.

Beispiel:

public class FooEnumerator : IEnumerator<int>
{
private int[] _dump = null;
private int currentIndex = -1;
internal FooEnumerator(int[] dump) { _dump = dump; }
public int Current { get { return _dump[currentIndex]; } }
public void Dispose() { /* Nicht benutzt */ }
object IEnumerator.Current { get { return Current; } }
public bool MoveNext() { return ++currentIndex < _dump.Length; }
public void Reset() { currentIndex = -1; }
}

Nun muss die Hauptklasse diesen Enumerator nur noch instanzieren und zurückgeben.

public class Foo : IEnumerable<int>
{
// … bisschen code …
public IEnumerator<int> GetEnumerator()
{
return new FooEnumerator(_dump);
}
// … bisschen code …
}

Veröffentlicht unter Coding | Verschlagwortet mit ,

C# Basics Teil 2

Auch im zweiten Teil der C# Basics, gehe ich nur auf die Spezialitäten ein.

Flag-Enumerations

Enumerationen können auch benutzt werden, um Kombinationen daraus zu erstellen. Typischerweise sind die Werte immer von der Basis 2. Diese sollten mit dem Attribute „Flag“ gekenntzeichnet werden. Dies erhört auf der einen Seite das Verständnis, welche Enums kombiniert werden dürfen und die ToString() Methode kann (und wird) darauf reagieren.

Beispiel:

[Flags]
public enum Sides { Left = 1, Right = 2, Top = 4, Bottom = 8, TopLeft = Left | Top }

Generic Methods

Generische Klassen sind nun schon länger bekannt. Die generischen Methoden werden vielmals vergessen und nur wenig verwendet. Das folgende Beispiel zeigt eine generische Methode, die zwei Objekte eines beliebigen Types vertauscht. Der Vorteil dabei ist, dass die Methode nur typisiert verwendet werden kann, was vom Compiler überwacht wird.

Beispiel:

static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}

// Der Aufgruf der Methode wiefolgt
int a = 10;
int b = 20;
Swap<int>(ref a, ref b);

Generic Default Value

Das default() Schlüsselwort kann benutzt werden, um den Default-Value eines generischen Argumentes zu bestimmen. Ist das generische Argument ein Referenz-Type, wird der default immer null sein. Handelt es sich um einen Value-Type, ist der Default-Value das Resultat des bitweisen nullen der Struktur (dies hat damit zu tun, wie Strukturen in C# behandelt werden).

static void SetToDefault<T>(List<T> list)
{
for (int i = 0; i < list.Count; i++)
list[i] = default(T);
}

Kovarianz bei Generics

Generische Typen sind nicht kovariant. Dies bedeutet, dass auch wenn ein Typ B nach A gecastet werden könnte ein Generic T<B> nicht nach T<A> gecastet werden kann. Nicht so bei den Arrays. Ein Typ B[] kann in einen Typen A[] gecastet werden. Dieser Zustand bei den Generics kann die Wiederverwendbarkeit ziemlich einschränken.

Beispiel:

class A {}
class B : A {}
A[] a1 = new B[10];  // funktioniert!
List<A> a2 = new List<B>();   // kompiler-error

Möchte man nun eine Methode implementieren, die eine Liste von A’s und B’s verarbeiten kann, käme man zuerst auf folgende Definition

static void Foo(List<A> a) { }

Doch weil List<A> ein Generic ist und nicht kompatibel zu List<B> würde ein Aufruf Foo(new List<B>()); nicht funktionieren. Hier schafft .NET Abhilfe durch ein spezielles Konstruct. Es definiert eine generische Methode mit einem where-Constraint, damit nur A und Ableitungen davon benutzt werden können. Danach funktioniert der Aufruf.

static void Foo<T>(List<T> t) where T : A { }

Veröffentlicht unter Coding | Verschlagwortet mit ,

C# Basics Teil 1

In den ersten paar Artikeln werde ich mich mit den C# Basics befassen. Dabei versuche ich nur auf Spezialitäten einzugehen, die man im Altag gerne vergisst – jedoch in bestimmten Situationen sehr hilfreich sein können.

Der checked/unchecked operator

Mit dem checked/unchecked Operator kann das Verhalten bei arithmetischen Überläufen definiert werden. Der Operator kann entweder als Expression oder Statement Block benutzt werden.

checked(<expression>) oder checked { /* statements */ }

Beispiel:

int a = int.MaxValue;
int b = a + 1; // b = -2147483648
b = checked(a + 1); // OverflowException
b = unchecked(a + 1); // b = -2147483648, auch mit Kompiler-Option /checked
b = int.MaxValue + 1; // Compiler-Error
b = unchecked(int.MaxValue + 1); // Kein Compiler-Error

Der params Parameter Modifier

Der params Parameter Modifier kann an einen letzten Parameter von einer Methode definiert werden. Der entsprechende Parameter muss jedoch vom Typ Array (z.B. int[]) sein. Der Compiler kann dann die letzten aktuellen Parameter in einen formalen Array-Parameter umwandeln.

Beispiel:

private int Sum(params int[] values)
{
int sum = 0;
foreach (int i in values)
sum += i;
return sum;
}

private void Calculate()
{
int sum = this.Sum(10, 44, 278);  // wird vom Compiler in int[] umgewandelt
sum = this.Sum(new int[] { 10, 44, 278 }); // Aufruf ohne params modifier
}

Boxing und Unboxing

Boxing und unboxing ist recht schnell erklärt. Boxing wird die Aktion genannt, wenn ein Value-type in einen Reference-type gecastet werden muss resp. ungekehrt.

Beispiel:

int x = 10;
object o = x;   // boxing
int b = (int)o; // unboxing

Schlüsselwort Unsafe

Ausführen von Programcode, welcher nicht durch die CRL kontrolliert wird. Damit lässt sich performanteren Code generieren, der weniger Overhead produziert. Doch er ist auch gefährlich und sollte nur in wenigen speziellen Bereichen verwendet werden. Ein weiterer Vorteil davon ist, dass man besser mit anderem unmanaged Code kommunizieren kann.

Beispiel:

unsafe // Kann nur mit Kompileranweisung /unsafe benutzt werden
{
int x = 10;
int* p = &x; // Adresse von x in den int-pointer p speichern (Referenzierung)
int y = *p;  // Wert an Adresse des int-pointers p in y speichern (Dereferenzierung)
p->CompareTo(y);   // Methodenaufruf direkt auf dem Pointer
(*p).CompareTo(y); // Methodenaufruf auf dem dereferenzierten Objekt
}

Noch ein paar Randbemerkungen

Der Adress-Operator „&“ ist nur bei Value-Typs notwendig, da es sich bei Referenz-Typen ja bereits um Referenzen handelt.

An die Adressen von Referenz-Typen kann man nicht so ohne weiteres kommen, da diese vom Garbage Collector aus Gründen der Effizienz immer wieder hin und her bewergt werden. Um sie zu verwenden muss man mit dem fixed-Statement arbeiten. Mehr dazu in einem späteren Artikel.

Veröffentlicht unter Coding | Verschlagwortet mit ,

OpenOffice 3.0

Seit dem 13.10.2008 ist nun endlich das neue OpenOffice 3.0 verfügbar.

Obwohl ich seit einiger Zeit ein Fan von OpenOffice bin, fällt mir eines an diesem Produkt auf. Es hinkt immer dem Microsoft Office hinterher. Schlecht implementierte Features vom Microsoft Office werden noch schlechter kopiert. Doch das coole neue Ribbon-Konzept von Microsoft wird offenbar noch ignoriert. Schade, schade.

Meiner Meinung nach, könnte es sich OpenOffice schon leisten ein paar eigene Gedanken mehr ins Produkt zu investieren und nicht immer nur links resp. rechts zu gucken.

Immerhin gibt es ein paar Features, die schon lange fällig waren:

  • Neue Icons resp. neues Look & Feel
  • Neue Aqua-Version für den Mac ab OS X (ohne X11-Server)
  • Öffnen von den neuen Microsoft Formaten OOXML (z.B. docx, xlsx). Eine Speicherung fehlt aber noch.
  • Erweiterung zum Importieren von PDF Dateien
  • Erweiterung einer Presenter Console fürs Impress
  • Start-Center (naja…)

Abschliessend möchte ich betonen, dass ich das neue OpenOffice niemandem verderben will. Alles in allem ist es ein grossartiges Produkt.

OpenOffice 3.0 wurde bis heute auf über 5 Millionen PCs weltweit installiert. Doch diese Zahl erscheint lächerlich, wenn man bedenkt, dass das Microsoft Office-Paket bei mehr als 550 Millionen Nutzern im Einsatz ist. Der Marktanteil beläuft sich also auf knapp 1%. Mal schauen, ob OpenOffice die prognostizierten 40% Marktanteil bis 2010 erreichen werden… ich hab da so meine Zweifel 🙂

Das ‚Neue‘ von .NET

Das neue .NET Framework 3.x ist (schon lange) da. Zu meiner Schade kann ich mir erst jetzt langsam damit befassen. Ich begann heute das Buch C# 3.0 in a Nutshell zu lesen. Ich werde versuchen wöchentlich einen kleinen Beitrag daraus zusammen zu fassen, damit Interessierte mitlesen können. In wieweit solche Blog-Einträge dann auf Begeisterung stossen, wird sich zeigen. Doch ich bin sicher, dass auch langjährige Entwickler ab und zu auch noch was spannendes entdecken können.

Veröffentlicht unter Coding | Verschlagwortet mit ,