C# Basics – letzter Teil

Der letzten Teil der Basics fällt eher mager aus…

Die Environment Klasse

Die Environment Klasse bietet nicht nur das bekannte NewLine an, sondern noch diverse andere hilfreiche Eigenschaften:

CurrentDirectory
Kompletter Pfad des Arbeitsverzeichnisses des Prozesses.
SystemDirectory
Kompletter Pfad zum Systemverzeichnis.
CommandLine
Kommandozeile des Prozesses.
MachineName
Beinhaltet den NetBIOS Namen der Maschine.
ProcessorCount
Beinhaltet die aktuelle Anzahl Prozessoren des Systems.
OSVersion
Beinhaltet das aktuelle OperatingSystem Objekt.
ExitCode
Der Exit-Codes des Prozesses.
HasShutdownStarted
Beinhaltet den Wert, ob die CRL oder die Applikation gerade heruntergefahren wird.
StackTrace
Beinhaltet die aktuellen StackTrace Informationen.
TickCount
Ermittelt die Anzahl Millisekunden seit Systemstart.
UserDomainName
Name der Netzwerk-Domäne in welcher der Benutzer angemeldet ist.
UserInteractive
Indikator, ob der aktuelle Prozess im interaktiven Modus ausgeführt wird. Ein Prozess, der im IIS gehostet wird, würde false zurückgeben, da keine Benutzerinteraktion aktiv ist.
UserName
Benutzernamen der Person ab, die derzeit beim Windows-Betriebssystem angemeldet ist.
WorkingSet
Größe des physischen Speichers ab, der dem Prozesskontext zugeordne ist.
Version
Beinhaltet das Version Objekt von der Applikation. Daraus lassen sich Major, Minor, Build und Revision Nummern lesen.

Seltene Operatoren

Folgende Operatoren sind in den meisten C# Programmen eher seltene Gäste, weil sie in den meisten Fällen nur in speziellen Situationen gebraucht werden.

-> : Pointer to struct (unsafe)
Wird benutzt, um an die Methoden eines Pointers zu kommen
Beispiel: p->ToString();   // wenn p ein Pointer in einem unsafe-Block ist

* : Value at address (unsafe)
Wird benutzt, um an den Wert an einer Memory-Adresse zu kommen
Beispiel: int a = *p;   // wenn p ein Int-Pointer in einem unsafe-Block ist

& : Address of value (unsafe)
Wird benutzt, um an die Memory-Adresse eines Objektes zu kommen
Beispiel: int* p = &i;   // wenn i ein int Objekt ist

~ : Bitwise complement
Wird benutzt, um an den Komplementärwert zu kommen
Beispiel: int a = ~0;   // Ergibt das Komplement -1

% : Remainder
Wird benutzt, um den Restwert einer Division zu erhalten
Beispiel: int a = 4 % 3   // Ergibt den Restwert 1

<< oder >> : Shift left oder right
Wird benutzt, um einen Wert bitweise nach links oder rechts zu verschieben
Beispiel: int a = 8 << 2;   // Ergibt 32 (8 wird bitweise 2 mal nach links verschoben)

^ : Logical Xor
Wird benutzt, um einen Wert bitweises exklusives ODER zu rechnen
Beispiel: int a =  15 ^ 3; // Ergibt 12 oder binär 1100

?? : Null Coalescing
Wird benutzt, um den linken Operanden zurückgegeben, falls dieser nicht null ist. Ansonsten wird der rechte Operand zurückgegeben.
Beispiel: object x = null ?? new object();   // x wird das neue Objekt enthalten, da der erste Operand null war

?: : Conditional
Wird benutzt, um abhängig vom Wert eines booleschen Ausdrucks einen von zwei Werten zurück
Beispiel: int a = 1 == 3 ? 10 : 20;   // Ergibt 20, da die Bedinung 1 == 3 falsch ist

Links, die man immer wieder braucht

Es gibt Links in die MSDN, die man immer wieder nachschlagen muss, weil gewisse Dinge einfach lange nicht benutzt werden. Hier eine kleine Sammlung davon:

Sprache C#

C# Schlüsselwörter

C# Operatoren

C# Präprozessordirektiven

C# Compileroptionen

Formatierungen

Zahlen-Formatzeichenfolgen (standard)

Zahlen-Formatzeichenfolgen (benutzerdefiniert)

DateTime-Formatzeichenfolgen (standard)

DateTime-Formatzeichenfolgen (benutzerdefiniert)

Enumerations-Formatzeichenfolgen

Veröffentlicht unter Coding | Verschlagwortet mit ,

C# Basics Teil 4

Hier der vierte Teil zu den Basics…

Nullbare Typen

Es ist allgemein bekannt, dass man Value-Typen nicht auf null prüfen oder damit initialisieren kann, da es sich ja nicht um Referenzen handelt. Nun, das ist grundsätzlich immer noch richtig. Doch C# hat ab 2.0 ein neues Feature eingebaut, damit man jede Art von Value-Typen “nullen” kann :-). Der Gebraucht ist sehr simpel. Jeder Typ kann mit einem Postfix in Form eines “?” zu einem nullbaren Typen gemacht werden.

Beispiel:

int a = null; // Kompiler-Error
int? b = null; // OK
if (b == null) { b = 1; }
if (b > 1) { b = 2; }

Warum funktioniert das?

Die Lösung des Rätzels ist einfach. Der C# Kompiler bedient sich einem neuen generischen Typen Nullable<T>. Bei der Kompilation wird der int? einfach in einen Nullable<int> umgewandelt. Ein int? ist so gesehen also kein wirklicher int mehr, sondern eine Instanz von Nullable, die den int hostet.

Man könnte sich nun fragen, warum dann ein Vergleich wie b > 1 noch funktioniert. Wenn der int? ja zu Nullable<int> transformiert wurde, dürfte ein Vergleich mit einem int nicht mehr funktionieren. Zudem Implementiert Nullable<> keine Operatoren <, > oder ==. Auch hier hat der Kompiler seine Hand im Spiel. Man nennt das ganze “Lifted Operators”. Der Kompiler klaut (oder hebt) den jeweiligen Operator vom darunterliegenden Typen an und schreibt einen neuen Ausdruck.

Beispiel:

int? a = 10; // OK
bool b = a > 5;

wird vom Kompiler zu folgendem Ausdruck übersetzt:

Nullable<int> a = new Nullable<int>(10);
bool b = a.HasValue ? a.Value > 5 : false;

Hinweis:

Vorsicht beim Vergleichen von Typen.

Nullable<int> a = 10;   // oder int?
bool isInt = a.GetType().Equals(typeof(int));   // true
bool isNullable = a.GetType().Equals(typeof(Nullable<int>));  // false!!!

Man sieht, obwohl die Variable a als Nullable<int> deklariert wurde, wird ein typeof(Nullable<int>) nicht funktionieren. Warum: a ist eine Instanz von Nullable<int>.  Der Kompiler “liftet” in diesem Falle die Operatoren mit denjenigen vom darunterliegenen Typen… und so auch den typeof-Operator!

Aber Vorsicht, es handelt sich dabei nie um einen wirklichen int. Prüft man die Instanz z.B. auf die Implementation eines IConvertible-Interface, wird dieser Test mit false quittiert, weil Nullable zwar die Operatoren liftet, jedoch die Schnittstellen der darunterliegenden Klasse in Ruhe lässt. Obwohl es bei der Konvertierung eigentlich keine Probleme geben sollte, gibt es im Framework eine spezielle Klasse dafür mit dem Namen NullableConverter.

Das Schlüsselwort fixed

Den meisten wird das Schlüsselwort fixed noch nie begegnet sein. Warum ist einfach zu erklären. Hierbei handelt es sich um eine Spezialität im Bereich von unsicherem Code.

Wozu dient es: Der GC (garbage collector) schiebt aus Effizienz- und Platzgründen während der Programmausführung die Objekte im Speicher hin und her und das Memory fragmentiert. Dies kann in gewissen Fällen Proleme verursachen, wenn man in einem mit unsafe markierten Codestück gerade dabei ist über die Memory-Adressen zu loopen. Mit dem fixed-Statement teilt man dem GC mit, dass er die Objekte darin in Ruhe lassen soll. Dies steigert zudem die Effizienz beim Durchlauf.

Beispiel:

unsafe
{
string myString = “Hallo”;
fixed (char* p = myString) { *p = ‘A’; }
}

Das Objekt myString wird vom GC innerhalb des fixed-Blockes in Ruhe gelassen. Man beachte zudem die Zuweisung der Adresse vom myString. Diese Zuweisung braucht keinen Adress-Operator “&”, da es sich beim String-Objekt ja bereits um eine Referenz handelt.

Veröffentlicht unter Coding | Verschlagwortet mit ,

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 ,