Peter Bucher Ralf Westphal

Blog

Nutze den Augenblick
und teile der Welt mit, was Du zu sagen hast.

Exceptions im Konstruktor

Mittwoch, 22. April 2009, 11:40 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Vor einigen Tagen wurde ich gefragt, ob man in .NET Exceptions innerhalb eines Konstruktors werfen dürfe, und wenn ja, was dabei zu beachten sei. Die Antwort auf diese Frage lautet wie so oft: Es kommt darauf an.

Um diese Frage beantworten zu können, muss zunächst zwischen normalen und statischen Konstruktoren unterschieden werden. Während erstere dazu dienen, ein Objekt zu initialisieren, sind letztere für Typen zuständig, weshalb sie häufig auch als Typeninitialisierer bezeichnet werden.

Ob nun innerhalb eines Konstruktors Exceptions geworfen werden dürfen, hängt davon ab, um welchen Typ es sich handelt:

Statische Konstruktoren

Statische Konstruktoren werden von der CLR garantiert nur ein einziges Mal ausgeführt, nämlich vor der ersten Verwendung des dazugehörigen Typen (zugegeben, es ist über Umwege tatsächlich möglich, einen statischen Konstruktor mehrfach auszuführen, aber in 99,9% der Fälle findet die Ausführung nur ein Mal statt).

Zudem muss ein statischer Konstruktor vollständig und erfolgreich durchlaufen werden, um einen Typ verwenden zu können. Die Konsequenz liegt auf der Hand: Wird innerhalb eines statischen Konstruktors eine Exception geworfen, kann der Konstruktor nicht vollständig und erfolgreich durchlaufen werden, was wiederum verhindert, dass der Typ genutzt werden kann.

Kurzum: Wird innerhalb eines statischen Konstruktors eine Exception geworfen, kann dieser Typ nicht mehr verwendet werden – es sei denn, die AppDomain wird neu gestartet.

Instanzkonstruktoren

Instanzkonstruktoren verhalten sich im Prinzip ähnlich: Auch diese müssen vollständig und erfolgreich durchlaufen werden, damit das zugehörige Objekt verwendet werden kann. Wird innerhalb eines solchen Konstruktors eine Exception geworfen, gibt der new-Operator keine Referenz auf das gerade erzeugte Objekt zurück, und dieses kann im weiteren Verlauf nicht verwendet werden.

Im Unterschied zu Typeninitialisierern können Instanzkonstruktoren allerdings beliebig oft aufgerufen werden, indem jeweils ein neues Objekt erzeugt wird: Schlägt die Initialisierung also fehl, genügt es, ein neues Objekt des gleichen Typs zu instanziieren, um den Konstruktor erneut auszuführen.

Insofern ist es durchaus zulässig und sogar guter Stil, Exceptions innerhalb von Instanzkonstruktoren zu werfen.

Besonderheiten

Dennoch gilt es, beim Werfen von Exceptions innerhalb von Instanzkonstruktoren zwei Dinge zu beachten:

  • Falls die zugehörige Klasse über einen Finalizer verfügt, muss dieser gegebenenfalls mit partiell initialisierten Objekten zurecht kommen und in jedem Fall unverwaltete Ressourcen wieder sauber freigeben.
  • Da im Falle einer Exception keine Referenz auf das neu erzeugte Objekt an den Aufrufer zurückgegeben wird, kann auf dem Objekt kein Dispose aufgerufen werden. Mit anderen Worten: Die Verwendung von using schlägt in einem solchen Fall fehl.

Review von RESTful .NET

Freitag, 10. April 2009, 10:16 Uhr
Permalink | Kommentare (3) | Kommentare als RSSRSS

In den vergangenen Tagen habe ich RESTful .NET von Jon Flanders gelesen – ein Buch über das Thema REST. Interessanterweise wird der Architekturstil REST allerdings nicht von Grund auf erklärt. Statt dessen konzentriert sich das Buch auf die Frage, wie REST-basierte Webservices in .NET 3.5 mit Hilfe von WCF modelliert werden können.

Der Effekt ist, dass das Buch angenehm kompakt ist, dennoch aber einen vollständigen und sehr verständlichen Einstieg in das Thema REST mit WCF bietet.

WCF war bislang für jemanden wie mich, der sich hauptsächlich mit Webentwicklung beschäftigt, ein verhältnismäßig komplexes Thema, bei dem man nie so recht wusste, wo man anfangen soll – zu viele Aspekte sind für reine Webentwicklung zunächst nicht von Bedeutung.

Mit RESTful .NET steht nun ein Buch zur Verfügung, das genau die Zielgruppe der Webentwickler bedient. Nach einer angenehm kurzen Einführung zum Thema REST gliedert sich das Buch in drei Teile:

  • Implementieren von REST-basierten Webservices: In den ersten fünf Kapiteln wird erklärt, wie das Lesen und Schreiben von Daten mit Hilfe von REST funktioniert, wie Methoden auf URIs gemappt werden können, und welche Möglichkeiten es gibt, REST-basierte Webservices zu hosten.
  • Anwendung von REST: Die nächsten fünf Kapitel widmen sich verschiedenen Anwendungsgebieten, die mit REST in WCF bedient werden können. Die Themen reichen dabei von Feeds über AJAX und JSON bis hin zu Silverlight.
  • REST und HTTP: Das letzte Kapitel geht schließlich auf die Zusammenhänge zwischen REST und WCF auf der einen und HTTP auf der anderen Seite ein, und klärt einige spezielle Fragen – wie beispielsweise HTTP-Statuscodes händisch gesetzt werden können.

Im Anhang findet sich zudem ein kompakter Überblick über die Neuerungen, die für WCF und REST in .NET 3.5 SP1 enthalten sind, was den Inhalt des Buches sehr schön ergänzt und abschließt.

Da es dem Autor gelingt, alle Aspekte sehr verständlich zu erklären, sind Vorkenntnisse zum Verständnis des Buches nicht zwingend notwendig, weshalb sich das Buch auch für Einsteiger in die REST-Thematik eignet.

Alles in allem – ein sehr empfehlenswertes Buch für jeden, der sich mit Webentwicklung beschäftigt, und dem WCF bislang zu Desktop-lastig war.

Interfaces vs abstrakte Basisklassen

Mittwoch, 1. April 2009, 09:37 Uhr
Permalink | Kommentare (10) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. April 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Interfaces vs abstrakte Basisklassen

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Interfaces haben in den vergangenen fünf Jahren einen ungeahnten Aufschwung erlebt. Ein – wenn nicht der – Grund hierfür liegt sicherlich in der zunehmenden Verbreitung von Microkernel-Architekturen, denen unter .NET mit Projekten wie beispielsweise Unity und autofac Rechnung getragen wird.

Allerdings ist auch die Entwicklung eines Microkernel im Eigenbau inzwischen keine hochwissenschaftliche Angelegenheit mehr – Microkernel sind also ein weiteres Thema, das nach und nach seinen elitären Status verliert und zu einer Commodity wird.

Bei all der Beschäftigung mit Interfaces verlässt ein anderes Sprachkonstrukt allerdings häufig all zu leicht das eigene Blickfeld: Abstrakte Basisklassen. Diese haben – der zunehmenden Fokussierung auf Schnittstellen zum Trotz – nach wie vor ihre berechtigte Existenz.

Die Frage lautet also: Wann bietet sich der Einsatz von Interfaces an, wann der einer abstrakten Basisklasse?

Um diese Frage beantworten zu können, empfiehlt es sich, sich zunächst einen Überblick über die jeweiligen Vor- und Nachteile zu verschaffen. Die Vorteile abstrakter Basisklassen liegen auf der Hand:

  • Polymorphie: Da abstrakte Basisklassen selbst nicht instanziiert werden können, müssen davon abgeleitete konkrete Klassen erstellt werden, die daraufhin ihrerseits instanziiert werden können. Da all diese konkreten Klassen aber über die gleiche abstrakte Basisklasse verfügen, kann die abstrakte Basisklasse zur Laufzeit an Stelle einer konkreten Klasse verwendet werden. Dieses Verhalten wird als Polymorphie bezeichnet und geht einher mit dem zweiten Vorteil.
  • Schnittstelle: Eine abstrakte Basisklasse definiert einen gemeinsamen Kontrakt für sich und die von ihr abgeleiteten Klassen. In jedem Fall liegt also eine wohldefinierte Schnittstelle vor. Sofern die abstrakte Basisklasse ihrerseits Code enthält, ist diese Schnittstelle potenziell nicht nur syntaktischer, sondern sogar semantischer Natur.
  • Codebasis: Da es in einer abstrakten Basisklasse möglich ist, nicht nur die Definition einer Schnittstelle vorzunehmen, sondern auch Code zu hinterlegen, bietet es sich an, Code, der in allen abgeleiteten Klassen gemeinsam genutzt werden soll, in einer abstrakten Basisklasse zu platzieren, um Redundanzen zu vermeiden. Zudem steht es dem Entwickler der abstrakten Basisklasse frei, nur die Klasse selbst als abstrakt zu markieren, nicht jedoch die darin enthaltenen Methoden. Das bedeutet, dass für abgeleitete Klassen kein Vererbungszwang besteht, was sich wiederum positiv auf die Versionierbarkeit niederschlägt.

Neben all diesen positiven Aspekten gibt es jedoch auch zwei essenzielle Nachteile, die den Einsatz einer abstrakten Basisklasse potenziell von vornherein verbieten können:

  • Einfachvererbung: Da .NET (und damit auch C#) keine Mehrfachvererbung unterstützen, schließt der Einsatz einer abstrakten Basisklasse den Einsatz einer anderen Basisklasse aus. Gelegentlich ist dies jedoch aus technischen Gründen zwingend erforderlich, so dass eine abstrakte Basisklasse nicht genutzt werden kann. Dies ist beispielsweise bei der Framework-eigenen Klasse MarshalByRefObject der Fall – muss von ihr abgeleitet werden, hat man als Entwickler mit einer eigenen abstrakten Basisklasse schlechte Karten.
  • is-a: Auch für abstrakte Basisklassen gilt, dass es sich bei der Ableitung von ihnen um eine is-a-Relation handelt, deren Einsatz im objektorientierten Paradigma wohlüberlegt sein will. Beispielsweise ist ein Quadrat im objektorientierten Sinn eben kein Rechteck, da sich die Semantik in einigen Punkten unterscheidet.

Beide Nachteile können durch Interfaces als leichtgewichtige Alternative ausgeräumt werden:

  • Mehrfachvererbung: Da Interfaces nur eine syntaktische, in keinem Fall jedoch eine semantische Schnittstelle definieren, kann eine Klasse beliebig viele Interfaces zugleich implementieren. Falls tatsächlich zwei Interfaces eine Methode mit gleicher Signatur enthalten sollten, kann die Eindeutigkeit durch explizite Implementierung wiederhergestellt werden.
  • has-a: Im Gegensatz zu einer Vererbung von einer Klasse beschreibt ein Interface lediglich eine Eigenschaft oder eine Fähigkeit, über die eine implementierende Klasse verfügt. Es handelt sich also nur um eine (semantisch schwächere) has-a-Relation, was den Aufbau komplexer Klassenhierarchien deutlich vereinfacht.

Doch auch Interfaces haben einen essenziellen Nachteil:

  • Codebasis: Interfaces können keinen Code enthalten, was zum einen wiederum Redundanzen und Codeduplizierung fördert. Zum anderen können Methoden in Interfaces nur ausschließlich als abstrakt definiert werden. In einer implementierenden Klasse besteht also Vererbungszwang, was die Versionierbarkeit eines Interfaces häufig unmöglich macht, in jedem Fall aber zumindest stark einschränkt.

Um nach all diesen Vorüberlegungen auf die ursprüngliche Frage zurückzukommen: Wann empfiehlt sich nun der Einsatz von Interfaces, wann der einer abstrakten Basisklasse?

Spielen die jeweiligen Nachteile keine Rolle, fällt die Entscheidung wenig überraschend nicht sonderlich schwer. Vermeintlich schwierig wird es, wenn sie eine Rolle spielen – doch wie gesagt, dies ist nur vermeintlich so: Der entscheidende Punkt ist nämlich, dass sich der Einsatz eines Interfaces und einer abstrakten Basisklasse nicht gegenseitig ausschließen.

Statt sich also auf eine der beiden Möglichkeiten einzuschränken, werden einfach beide Varianten genutzt: Aus einem oder wird ein und.

Zunächst wird dementsprechend ein Interface entwickelt, das als grundlegende syntaktische Basis dient. Darauf aufbauend wird dann eine abstrakte Basisklasse entwickelt, die eine Standardimplementierung dieses Interfaces enthält.

Konkrete Klassen haben nun die Wahl, ob sie die Freiheit eines Interfaces oder den Komfort einer abstrakten Basisklasse benötigen, und können je nach Bedarf so oder so ableiten. Die Polymorphie bleibt dabei über das Interface in jedem Fall erhalten, so dass zu einem späteren Zeitpunkt die Implementierung eines Interfaces sogar vollkommen problemlos gegen die Ableitung von einer Basisklasse (oder umgekehrt) ausgetauscht werden kann.