Heute widme ich mich mal wieder zwei Bestandteilen von .Net die es schon sehr lange gibt und deren Nutzung als Grundlagenwissen zu verstehen sind. Da mir recht häufig der „Missbrauch“ dieser Dinge unterkommt, will ich hier die Vorteile und Nachteile etwas genauer beleuchten. Dabei geht es mir zunächst um das Schlüsselwort as und im Weiteren um Exceptions allgemein.

as gibt es seit .Net 2.0 und es ermöglicht das Casten von Objekten auf einen bestimmten Typ. Ein Vorteil dabei ist, dass ein solcher Cast wesentlich lesbarer ist, als bei der allgemeinen Methode. Ein weiterer vermeintlicher Vorteil besteht darin, dass keine Exception geschmissen wird wenn der Cast „missglückt“.

Nachfolgender Code zeigt kurz beide Varianten. Am Ende der Ausführung ist str1 null und str2 gar nichts, denn an dieser Stelle fliegt eine InvalidCastException und beendet die Applikation.

object obj = new object();
string str1 = obj as string;
string str2 = (string) obj;

Laut MSDN, kann man das as Schlüsselwort in unserem Beispiel auch mit folgendem Konstrukt gleichsetzen. Es wird also überprüft, ob ein Objekt tatsächlich vom angegebenen Typ ist. Ist dem so, erfolgt ein Cast ansonsten wird null zurückgegeben.

string str1 = obj is string ? (string) obj : (string) null;

Nun gut, wenn es so wahnsinnig toll ist warum mag ich „as“ dann nicht? Eben genau wegen diesen vermeintlichen Vorteilen und eben weil es genau wegen diesen in meinen Augen von einigen Leuten inflationär eingesetzt wird.

Zugegeben über die Lesbarkeit von Code kann man sich viel und lange streiten. Das Nichtwerfen von Exceptions als Vorteil zu deklarieren zeugt für mich aber von einer falschen Arbeitseinstellung. Denn wann immer ein Fehler geschieht der im Produktivcode nicht geschehen darf ist eine Exception zu werfen!

Wie sollen wir sonst darauf aufmerksam werden, dass etwas nicht stimmt? In meinen Augen müssen sich einige Entwickler davon verabschieden, Exceptions als Teufelszeug anzusehen nur weil sie uns die eigenen Fehler vorhalten. Denn genau das ist ihre Aufgabe.

Sie sollen uns auf Bugs aufmerksam machen, bevor diese in ungünstigen Situationen (also zum Beispiel beim Kunden) auftreten und sie sollen uns helfen Fehlsituationen leichter zu lokalisieren. Das dabei ein gewisses Maß bewart werden muss und ab jetzt nicht wild mit Exceptions geschmissen werden darf, versteht sich von selbst. Gänzlich auf sie zu verzichten oder sie in jedem Fall mit Try-Catch mundtot zu machen ist aber einfach falsch.

Nachfolgender Code zeigt dies recht deutlich und entspricht dem Auslöser für dieses Post. Dabei muss man wissen, dass die Klasse B von A abgeleitet ist.

public void Foo()
{
    A a = new A();
     // ...
     DoSomething(a as B);
}
public void DoSomething(B b)
{
    if (b == null)
    {
        throw new ArgumentNullException("b");
    }
     // ...
}

Ungeachtet dessen, dass wir ein A erstellen und es einer Methode übergeben die ein B verlangt, wird der Code kompiliert. Bei der Ausführung erhalten wir jedoch eine ArgumentNullException. Diese Exception ist berechtigt immerhin sollten public Methoden immer prüfen ob ihre Eingangsparameter ungleich null sind. Ansonsten fliegt an späterer Stelle eine NullReferenceException und dann muss erst lange gesucht werden warum die Referenz denn nun keinen Wert hat.

Die Problematik der verschleppten Informationen hat man im Beispiel aber bereits, da ein null-Wert an die Methode übergeben wird statt eine InvalidCastException zu werfen. Durch die Nutzung von as wird die Aufmerksamkeit also weg vom falschen Cast, als Wurzel des Problems, hin zum falschen Parameter gelenkt. Man sucht demnach in der Methode DoSomething nach einem Fehler obwohl jener viel früher aufgetreten ist.

Noch einmal deutlich: Hätte man hier einen echten Cast verwendet, wäre eine InvalidCastException geflogen, man hätte nur in den Stacktrace jener schauen müssen und sofort den Ausgangspunkt des Fehlers gefunden. Das wiederum hätte weniger Zeit gekostet, vor allem wenn man den Fehler nur in einem Log und nicht während einer Debugsession findet.

Aufgrund der NullReferenceException-Problematik habe ich as mittlerweile fast gänzlich aus meinem C#-Wortschatz getilgt. Sollte man sicher gehen wollen, dass kein unzulässiger Cast geschieht, kann man immerhin auch is verwenden. Aber auch da kann man Mist bauen, wie folgendes Beispiel zeigt:

public bool DoSomething(A a)
{
    bool returnValue = false;
    if (a is B)
    {
        B b = (B)a;
        // ...
        returnValue = true;
    }
    return returnValue;
}

Dies basiert auf realem Code der mir Tränen in die Augen trieb als ich ihn sah. Denn hier wird jeglicher Fehler verschleppt. Es gibt Situationen in denen es sich lohnt per Returnwert über den Erfolg oder Misserfolg einer Aktion zu informieren ohne gleich eine Exception zu werfen (siehe hier).

Woher soll aber die aufrufende Methode in diesem Fall wissen, dass sie ein false kassiert weil der Cast nicht möglich ist? Wie soll das false behandelt werden? Welche Meldung zeigt man ggf. dem Nutzer an? Im schlimmsten Fall wird hier der Rückgaberwert nicht geprüft, der Fehler bleibt somit unentdeckt und kann sich fortpflanzen, was dann in irgend welchem wirren Verhalten endet welches niemand mehr nachvollziehen kann.

Im Beispiel, kann man sich das aber getrost schenken wenn man als Parameter gleich den richtigen Typ verlangt. Sollte man verschiedene Casts in der Methode haben, kann man evtl. auch nachfolgendes probieren. Wobei C und B wieder von A abgeleitet sind.

public void DoSomething(A a)
{
    if (a is B)
    {
        // ...
    }
    else if (a is C)
    {
        // ...
    }
    else
    {
        throw new ArgumentException("not supported class type "+a.GetType());
    }
}


Es ist also zunächst immer wichtig die Rahmenbedingungen einer Aktion zu prüfen und eine Exception nur dann auszulösen wenn ein Fehlerfall eintritt durch den jene Aktion nicht kontrolliert beendet werden kann. „Nicht kontrolliert“ heißt demnach: Auf eine vom Programmierer unvorhergesehene Weise. Denn dadurch kann die Richtigkeit des Ergebnisses jener Aktion nicht gewährleistet werden.