Kategorie: .NET


Webbrowser-Control: ObjectForScripting

20. Februar 2010 - 23:48 Uhr

Man wird alt, wie ein Sack, man baut gefühlte siebenhundert Applikationen, die das Webbrowser-Control verwenden – und dann stellt man fest, das man bisher viel zu viel Arbeit in dämliche Workarounds gesteckt hatte, nur weil man die Doku nie komplett gelesen hat.

So ging es mir heute, als ich über die ObjectForScripting-Eigenschaft des .NET-Webbrowser-Controls gestolpert bin, die es erlaubt, eine Klasse zu definieren, deren Methoden vom Webbrowser-Control bzw. darin enthaltenem JavaScript-Code per window.external.<Methodenname> angesprochen werden kann. Einzige Bedingung: Die Klasse muss COM-sichtbar sein, also das ComVisible-Attribut muss mit dem Wert true gesetzt sein.

Tja, hätte man mal vorher in der Doku nachgeschaut…

Kommentieren » | .NET

#SQLCLR: Rückgabenlänge von Userdefined Functions

19. Dezember 2009 - 10:17 Uhr

Wer mit der im SQL Server 2005 / 2008 integrierten .NET-Unterstützung arbeitet, will diese Funktionalitäten recht schnell nicht mehr missen – wird aber über kurz oder lang auch darüber stolpern, dass etwa die Rückgabelänge von Zeichenketten auf 8 KByte beschränkt ist. Das mag zwar nach viel klingen, aber wenn man ein XML-Fragment zurückgeben möchte, stößt man recht zuverlässig an diese Grenze und fängt sich eine TruncationException ein.

Eine gewöhnliche Userdefined Function in C# sieht etwa so aus:

[SqlFunction]
public static SqlString Foo(String input) { ... }

Sowohl Rückgabe, als auch der Übergabeparameter input sind hier auf eine Größe von maximal 8 KByte beschränkt. Wem das nicht ausreicht, der muss das SqlFacet-Attribut verwenden, das sich sowohl oberhalb der Funktion, als auch vor dem Übergabeparameter notieren lässt:

[SqlFunction]
[return: SqlFacet(MaxSize = -1)]
public static SqlString Foo([SqlFacet(MaxSize = -1)] String input) { ... }

Problem gelöst: Die MaxSize-Angabe definiert, dass es keine maximale Größe für den Parameter oder die Rückgabe mehr geben soll. Nach dem erneuten Bereitstellen der Assembly im SQL Server sollte es keine TruncationException mehr geben.

Kommentieren » | .NET, SQL Server, Tipp

#.NET: #LINQ to SQL und XML-basierte Mappings

30. Juli 2009 - 14:34 Uhr

Was von Anfang an bei LINQ to SQL gestört hat, ist die scheinbare Notwendigkeit, auf Mappings per Attribut zurückzugreifen. Stellen Sie sich ein klassisches Szenario vor: Es sollen mit den selben LINQ-Statements je nach Situation unterschiedliche Tabellen abgefragt werden (ich habe das zum Beispiel bei einem Kunden, der mit Navision arbeitet). Das geht mit Attributen schlicht nicht. Oder Sie möchten einfach keine Attribute nutzen, die ja eine fixe Verdrahtung mit LINQ to SQL bedeuten würden, was oftmals nicht wünschenswert ist.

Zum Glück gibt es die XmlMappingSource-Klasse. Die erlaubt es, die Mappings in XML-Dateien abzulegen und somit unabhängig von der Klasse zu halten, was in Bezug auf Wartbarkeit und Entkoppelung von Schichten ohnehin deutlich sinnvoller ist.

Die XmlMappingSource-Klasse verfügt über vier statische Methoden:

  • FromReader(): Nimmt eine XmlReader-Instanz entgegen
  • FromStream(): Erwartet einen Stream
  • FromUrl(): Erwartet die Angabe eines URI
  • FromXml(): Akzeptiert eine als XML interpretierbare Zeichenkette

Etwas umständlich ist, das nicht mehrere XML-Dateien zugewiesen werden können, aber da kann man sich ggf. mit kleineren Workarounds (Laden mehrerer Dateien, Überführen in ein Gesamt-XML) behelfen.

Um zu verdeutlichen, wie mit dem XML-basierten Mapping gearbeitet kann, soll im folgendenen Beispiel eine einfache Personen-Klasse verwendet werden:

using System;

namespace XML_LINQ_Sample.Model
{
   /// <summary>
   /// Repräsentiert eine Person
   /// </summary>
   public class Person
   {
      /// <summary>
      /// Initialisierung der Klasse
      /// </summary>
      public Person()
      {
         LastChanged = DateTime.Now;
      }

      /// <summary>
      /// Id der Person, Identity in der Tabelle
      /// </summary>
      public int Id { get; set; }

      /// <summary>
      /// Vorname
      /// </summary>
      public string FirstName { get; set; }

      /// <summary>
      /// Nachname
      /// </summary>
      public string LastName { get; set; }

      /// <summary>
      /// E-Mail-Adresse
      /// </summary>
      public string Email { get; set; }

      /// <summary>
      /// Datum der letzten Änderung
      /// </summary>
      public DateTime LastChanged { get; set; }
   }
}

Das Mapping für diese Klasse kann nun wie folgt aussehen:

<Database Name="Samples"
          xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">

   <!-- Person -->
   <Table Name="Persons" Member="XML_LINQ_Sample.Model.Person">

      <!-- Typ-Definition -->
      <Type Name="XML_LINQ_Sample.Model.Person">

         <!-- ID -->
         <Column Name="Id" Member="Id" IsPrimaryKey="true" IsDbGenerated="true" />

         <!-- Vorname -->
         <Column Name="FirstName" Member="FirstName" />

         <!-- Nachname -->
         <Column Name="LastName" Member="LastName" />

         <!-- Email -->
         <Column Name="EMail" Member="Email" />

         <!-- Letzte Änderung -->
         <Column Name="LastChanged" Member="LastChanged" />
      </Type>

   </Table>

</Database>

Wichtig für dieses Mapping ist die Angabe des korrekten Namensraumes http://schemas.microsoft.com/linqtosql/mapping/2007, da der Inhalt des XML-Dokuments sonst schlicht nicht ausgewertet werden könnte. Die einzelnen Typen werden innerhalb eines Table-Elements in einem Type-Element definiert. Eine Spalte definiert das Column-Element, wobei die beiden Attribute Name und Member Pflichtangaben sind. Zusätzlich werden beim ID-Element die beiden Attribute IsPrimaryKey und IsDbGenerated verwendet. Deren Aufgabe ist es, anzugeben, ob die Spalte den Primärschlüssel der Tabelle repräsentiert (IsPrimaryKey) und ob der jeweilige Wert vom Datenbanksystem automatisch generiert werden soll (IsDbGenerated).

Tipp: Das XML-Dokument sollten Sie als Ressource ihrem Projekt hinzufügen, da dies die Handhabung deutlich erleichtert. In diesem Fall wird es unter dem Namen PersonMapping als Ressource gespeichert.

Innerhalb unseres Programms können Sie nun eine Methode definieren, die diese Ressource lädt und einem DataContext zuweist:

/// <summary>
/// Erzeugt einen DataContext
/// </summary>
private static DataContext GetDataContext()
{
   // ConnectionString-Einstellungen abrufen
   ConnectionStringSettings connStr = ConfigurationManager.ConnectionStrings["Sample"];

   // DbProviderFactory abrufen
   DbProviderFactory factory = DbProviderFactories.GetFactory(connStr.ProviderName);

   // Connection erzeugen lassen
   DbConnection connection = factory.CreateConnection();
   connection.ConnectionString = connStr.ConnectionString;

   // MappingSource definieren
   XmlMappingSource source = XmlMappingSource.FromXml(Resources.PersonMapping);

   // DataContext zurückgeben
   return new DataContext(connection, source);
}

Der "magische Teil" ist die Verwendung einer XmlMappingSource. Ansonsten verhält sich der DataContext, wie Sie es gewohnt sind:

/// <summary>
/// Führt irgendwelche Operationen aus
/// </summary>
public void PerformActions()
{

   // DataContext definieren
   using(DataContext dc = GetDataContext())
   {
      // Schema erzeugen lassen, wenn es
      // noch nicht existiert
      if(!dc.DatabaseExists())
      {
         dc.CreateDatabase();
      }

      // Tabelle referenzieren
      Table<Person> personTable = dc.GetTable<Person>();

      // Datensatz finden
      Person person = (
         from Person p in personTable
         where p.LastName.Equals("Samaschke") &&
               p.FirstName.Equals("Karsten")
         select p).FirstOrDefault();

      // Datensatz gefunden?
      if(null != person)
      {
         // Werte ändern
         person.Email = "name@adresse.de";
         person.LastChanged = DateTime.Now;
      }
      else
      {
         // Datensatz anlegen
         person = new Person();
         person.FirstName = "Karsten";
         person.LastName = "Samaschke";
         person.Email = "name@adresse.de";

         // Datensatz einfügen
         personTable.InsertOnSubmit(person);
      }

      // Änderungen speichern
      dc.SubmitChanges();
   }
}

Mit dem Umweg über die XmlMappingSource schlagen Sie letztlich mehrere Fliegen mit einer Klappe:

  • Verwendbarkeit verschiedener Datenbanken oder Datenbanktabellen je nach Szenario
  • Verhinderung des Bindens von Klassen an ein bestimmtes O/R-Mapping-Framework
  • Bessere Wartbarkeit und Pflegbarkeit Ihres DataLayers

Einziger echter Nachteil ist, das Microsoft diesen Ansatz zwar grundsätzlich dokumentiert, Sie aber bis auf die Klassenbeschreibung der XmlMappingSource und die Definition des XML-Schemas für die Mapping-Dateien keinerlei weitere Unterstützung von Seiten des Anbieters erhalten. Da die Elemente und Attribute jedoch den Standard-LINQ-Code-Attributen entsprechen, können Sie die dort getroffenen Aussagen hier 1:1 übertragen.

Kommentieren » | .NET, Tipp, XML

#TIPP: #Fluent #Interfaces

29. Juli 2009 - 11:02 Uhr

Nicht ganz neu, aber ungemein praktisch ist die Idee von Fluent Interfaces. Der Gedanke dahinter ist sehr simpel: Alle Methoden haben eine Rückgabe vom Typ eines definierten Interfaces oder der jeweiligen Klasse. Der Effekt: Die Lesbarkeit wird gesteigert und der Schreibaufwand verringert sich.

Im folgenden Beispiel seien ein Interface IConfiguration und ein Interface IConfigurationFluent definiert, die die gleiche Funktionalität abbilden:

/// <summary>
/// Setzt Konfigurationsinformationen
/// </summary>
public interface IConfiguration
{
   /// <summary>
   /// Breite setzen
   /// </summary>
   void SetWidth(int width);

   /// <summary>
   /// Höhe setzen
   /// </summary>
   void SetHeight(int height);

   /// <summary>
   /// Text setzen
   /// </summary>
   void SetText(string text);

   /// <summary>
   /// Ausführen
   /// </summary>
   void Execute();
}

/// <summary>
/// Setzt Konfigurationsinformationen auf angenehmere Art und Weise
/// </summary>
public interface IConfigurationFluent
{
   /// <summary>
   /// Breite setzen
   /// </summary>
   IConfigurationFluent SetWidth(int width);

   /// <summary>
   /// Höhe setzen
   /// </summary>
   IConfigurationFluent SetHeight(int height);

   /// <summary>
   /// Text setzen
   /// </summary>
   IConfigurationFluent SetText(string text);

   /// <summary>
   /// Ausführen
   /// </summary>
   IConfigurationFluent Execute();
}

Der zusätzliche Definitionsaufwand ist minimal, die Handhabung des Codes nimmt jedoch deutlich zu. Sehen wir uns an, wie man mit dem herkömmlichen Interface arbeitet:

/// <summary>
/// Konfiguriert ein Element auf die "klassische" Art
/// </summary>
public void Configure(IConfiguration config)
{
   // Höhe setzen
   config.SetHeight(20);

   // Breite setzen
   config.SetWidth(40);

   // Text setzen
   config.SetText("Hallo, Welt");

   // Ausführen
   config.Execute();
}

Nichts dagegen einzuwenden. Störend ist nur, das Sie ständig "config" schreiben müssen und Operationen, die zusammen gehören, einzeln durchführen.

Hier ist der funktional gleichwertige Code unter Verwendung des Fluent-Interfaces:

/// <summary>
/// Konfiguriert ein Element auf die "fluente" Art
/// </summary>
public void Configure(IConfigurationFluent config)
{
   // Höhe, Breite, Text setzen und Operation durchführen
   config.SetHeight(20)
         .SetWidth(40)
         .SetText("Hallo, Welt")
         .Execute();
}

Hinweis: Da die implementierten Methoden alle die jeweilige Instanz wieder zurückgeben sollen, muss an deren Ende stets return this; eingefügt werden. Für die Methode SetHeight() könnte eine entsprechende Implementierung also so aussehen:

/// <summary>
/// Setzt die Höhe
/// </summary>
public IConfigurationFluent SetHeight(int height)
{
   // Wert setzen
   this.height = value;

   // Aktuelle Instanz zurück geben
   return this;
}

Wie gesagt: Funktional ist dieser Code gleichwertig, in der alltäglichen Anwendung aber um einiges angenehmer, als der klassische Code. Aus genau diesem Grund wird dieser Ansatz von immer mehr Frameworks eingesetzt – bekanntester Fall ist das NHibernate-Framework, das sehr extensiv davon Gebrauch macht.

6 Kommentare » | .NET, Handwerk, Tipp

#XML: Arbeit mit Namensräumen beim XmlDocument

28. Juli 2009 - 18:12 Uhr

Viele XML-Dokumente, die Sie im Web herunterladen oder benutzen können, verfügen über einen Namensraum. Den müssen Sie angeben, wenn Sie bei .NET die XmlDocument-Klasse und deren Methoden SelectSingleNode() bzw. SelectNodes() verwenden möchten, um an die für Sie relevanten Informationen mit Hilfe von XPath-Ausdrücken zu gelangen. Die Frage ist nur, wo und wie man das machen kann, denn das Dokument verfügt zwei über eine Eigenschaft NameTable, die kann jedoch scheinbar nicht sinnvoll verwendet werden.

Stattdessen greifen Sie auf die Klasse XmlNamespaceManager zurück. Deren Konstruktor nimmt als Parameter die NameTable des Dokuments entgegen. Die Methode AddNamespace() erlaubt es Ihnen im Anschluß, die für Sie relevanten Namensräume zu definieren. Dabei ist zu beachten, das so ein Namensraum stets aus zwei Komponenten besteht: Einem Präfix (der eine Abkürzung für den Namensraum darstellt) und dem URI, dem eigentlichen Namensraum. Das Präfix ist dabei – im Vergleich zum originalen Dokument – frei wählbar, der URI muss dem Namensraum im zu verarbeitenden Dokument entsprechen. Sie erkennen die Namensräume stets anhand der xmlns- und xmlns:xx-Attribute, wobei ersteres einen Standardnamensraum darstellt, der ebenfalls per XmlNamespaceManager-Instanz bekannt gemacht werden muss.

Folgendes Beispiel verwendet die XmlNamespaceManager-Klasse um eine Abfrage auf bestimmte Knoten in einem fiktiven Dokument umzusetzen:

// Dokument erzeugen
XmlDocument doc = new XmlDocument();

// Inhalt einladen
doc.Load("http://...");

// Namespace-Manager für die Verwaltung der Namensräume
XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable);

// Namensräume anfügen, wichtig sind die URIs
manager.AddNamespace("xs", "urn:sample"); //xmlns:xs="urn:sample"
manager.AddNamespace("xt", "urn:sample2"); //xmlns:xt="urn:sample2"

// Standard-Namensraum anfügen
manager.AddNamespace(string.Empty, "urn:default"); //xmlns="urn:default"

// Abfrage ausführen
XmlNodeList selected = doc.SelectNodes("//dummy[@xt:text='foo']/xs:name");

Die einzige wirkliche Schwierigkeit bei der Arbeit mit Namensräumen im .NET-Framework besteht letztlich nur darin, diese zu identifizieren. Nachdem Sie diese Leistung erbracht haben, sollten die weiteren Schritte problemlos umsetzbar sein.

Kommentieren » | .NET, Tipp, XML

#Mobile: Bug in IE6 Mobile / Webbrowser-Control beim .NET CF 3.5?

21. Juli 2009 - 12:41 Uhr

Entweder bin ich nicht in der Lage, mit dem Webbrowser-Control vom .NET CompactFramework umzugehen, oder es gibt da tatsächlich sowas wie einen Bug, wenn man es mit dem IE6 Mobile nutzen muss (was ja nicht in der Hand des Entwicklers liegt). Folgendes Szenario funktioniert bei mir schlicht nicht, wenn ich es mit einem IE6 Mobile im Emulator oder auch auf real existierenden Geräten (HTC Touch Pro2 beispielsweise) benutze:

Gegeben sei ein Formular mit einem darauf befindlichen Webbrowser-Control namens webBrowser1. Es gibt einen Eventhandler, der an das Navigating-Ereignis gebunden ist und nichts anderes machen soll, als die anzuspringende Adresse auszugeben:

private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
   MessageBox.Show(e.Url.ToString());
}

Beim Load-Ereignis des Formulars wird dem Webbrowser-Control über dessen .DocumentText-Eigenschaft ein einfacher HTML-Code zugewiesen:

private void Form1_Load(object sender, EventArgs e)
{
   string html =
      "<html><head><title>Sample</title></head><body><a href=\"http://www.microsoft.de\">Microsoft.de</a></body></html>";
   webBrowser1.DocumentText = html;
}

Auf einem Gerät mit dem (alten) IE4 gibt es zwei Anzeigen der MessageBox:

Windows Mobile 6 Professional Windows Mobile 6 Professional (2)

Wenn man es mit dem IE6 Mobile (etwa im Windows Mobile 6.5-Emulator) versucht, geschieht dies nicht – es wird nur die initiale MessageBox angezeigt:

WM 6.5 Professional - 240x400 WM 6.5 Professional - 240x400 (2)

Auch ein erneutes Binden des Ereignishandlers funktioniert nicht. Dabei – und das ist interessant – werden JavaScripts beispielsweise innerhalb der Webseite ausgeführt. Aber der Klick auf den Link führt zu keiner Reaktion – noch nicht einmal die referenzierte Webseite wird aufgerufen.

Bisher habe ich keinen Workaround darum gefunden. Ich weiß, dass das OpenCFFramework da was anbietet, aber ich sehe – wenn ich ehrlich bin – nicht wirklich ein, auf ein anderes Framework zu wechseln, wenn es sich doch um eine bereits in der Vergangenheit funktionierende Funktionalität handelt.

Nix da, ich nenne das einen Bug.

Kommentieren » | .NET, .NET CF, Mobile

#ASP.NET: Ausgabe manipulieren

11. Juli 2009 - 00:12 Uhr

Angenommen, Sie würden eine Webseite betreiben, die etwa BB-Code oder anderen Pseudocode für Formatierungen von Ausgaben verwendet. Sie hätten dann die unangenehme Aufgabe, ein entsprechendes Umformatieren aller BB-Code-Tags in die entsprechenden HTML-Tags bei jeder Ausgabe vorzunehmen. Da wäre es doch angenehmer, dies zentral erledigen zu lassen.

Für genau diesen Zweck ist die Eigenschaft Filter der HttpResponse-Klasse geschaffen worden. Diese erlaubt es, eine eigene Stream-Implementierung zu definieren, die eine Ausgabe … naja, filtert. Dabei ist das Schwierigste, diese Implementierung zu entwickeln, jedenfalls dann, wenn man nicht weiß, wie man es anstellen soll.

Die Lösung: Einfach die vorhandene Stream-Implementierung im Konstruktor des eigenen Streams übergeben lassen, in einer Instanzvariablen speichern und dann überall verwenden. Der eigene Filter erbt dabei zwingend von der Basisklasse System.IO.Stream und implementiert die abstrakten Methoden dieser Klasse.

Weiterlesen »

Kommentieren » | .NET, ASP.NET, Tipp

#Meinung: Warum Checked Exceptions in .NET fehlen

10. Juli 2009 - 00:06 Uhr

Anders Hejlsberg ist seines Zeichens der Erfinder von C#. In einem Interview, das er vor mehreren Jahren gegeben hat, lässt er sich über Checked Exceptions bzw. den Verzicht auf diese Checked Exceptions aus.

Zur Erklärung: Wenn innerhalb einer Methode einer .NET-Klasse eine Exception geworfen wird, dann muss ein Entwickler, der diese Methode aufruft, nichts machen. Er muss noch nicht mal irgendwie reagieren. Oftmals weiß er noch nicht einmal, das es in der Methode eine Exception geben könnte. Der Grund dafür liegt im Verzicht auf einen Mechanismus, der dem Entwickler zuverlässig mitteilt, das in dieser Methode eine bestimmte Exception auftreten kann und diese innerhalb der Methode auch nicht behandelt worden ist. Bei Java gibt es diesen Mechanismus – wenn man dort eine Methode definiert und innerhalb der Methode eine Exception per throw-Schlüsselwort wirft, muß man diese Exception per throws-Schlüsselwort auf Ebene der Methodendeklaration angeben. Das sieht dann etwa so aus:

public void tueWas(String input) throws MySuperDuperException, YourSuperDuperException { … }

Ruft man jetzt diese Methode auf und kümmert sich nicht um MySuperDuperException und YourSuperDuperException, dann weist einen der Java-Compiler dezent mit einem echten Fehler darauf hin. Kompilieren geht schlicht nicht, das geht erst dann, wenn entweder auf der aufrufenden Methode ebenfalls per throws angegeben worden ist, das diese Exceptions nicht behandelt werden oder wenn man eben das Naheliegende macht: try-catch verwenden. So oder so, als Entwickler ist man gezwungen, sich mit diesen Exceptions auseinander zu setzen.

Anders Hejlsberg sieht das anders. Er führt drei Punkte an, die seiner Meinung nach gegen diese Checked Exceptions sprechen: Versionierung, Skalierbarkeit und Unübersichtlichkeit. Gehen wir diese Punkte einmal der Reihenfolge nach durch.

1. Versionierung

Hier sagt Hejlsberg, das Interfaces unveränderlich sein sollten. Wenn man nun in der Weiterentwicklung eines Interfaces eine neue Exception einführt, dann würde das zwangsläufig zu Inkompatibilitäten und zusätzlichem Entwicklungsaufwand führen. Und die meisten Entwickler würden sich ohnehin nicht darum kümmern.

Ich sage, dass das eine komplette Nicht-Aussage ist, denn man führt Exceptions nicht mal einfach so ein (es spricht ganz definitiv nicht für eine durchdachte API, wenn die bei jedem Release geändert wird) und ganz nebenbei kann ich einfach eine neue Exception definieren, die von einer existierenden (und per throws deklarierten Exception) erbt. Dann muss auch nix neu implementiert werden. Das Argument ist schlicht nicht stimmig. Das Einzige, was hier stimmt, ist die Aussage, dass sich Entwickler ohnehin nicht darum kümmern würden – was nun aber letztlich auch eher gegen-, als für sie spricht und Zeichen einer recht unprofessionellen Arbeitseinstellung ist.

2. Skalierbarkeit

Hejlsberg führt aus, das die Checked Exceptions dazu führen würden, das die Entwickler viel zu viele Exceptions zu behandeln hätten und stattdessen eher auf generische try-catch(Exception e)-Statements wechseln würden. Damit hat er grundsätzlich sicher recht – die Programmierer machen das tatsächlich so. Aber aus diesem Grund auf die Checked Exceptions verzichten, nur weil ein paar Zeitgenossen schlicht keine passende Arbeitseinstellung haben? Das ist kein Argument.

Weiterhin merkt er an, das Checked Exceptions tendenziell eher lokaler behandelt werden. In einer großen Applikation sollte es aber ein zentrales Fehlerhandling geben. Soweit folge ich seiner Argumentation noch. Aber: Auch in Java ist es möglich, Exceptions erneut zu werfen und sie – nach der lokalen Behandlung – dem zentralen Fehlermanagement zuzuführen. Insofern greift seine Aussage schlicht nicht. Dazu kommt, das der Ansatz, ohne Checked Exceptions zu arbeiten, eher zu globalen try-catch(Exception e)-Statements oder noch besser try-finally-Statements führt – Fehlerbehandlung und die gezielte Reaktion auf Ausnahmen sieht schlicht anders aus. Das ist – zugespitzt formuliert – schlicht schlechtes und amateurhaftes Programmieren, was am Ende des Tages die Applikation unzuverlässiger und langsamer macht. Nö, das Argument kann man so nicht stehen lassen.

3. Unübersichtlichkeit

Dieses Argument hängt mit dem Skalierbarkeitsargument zusammen: Es wird sehr unübersichtlich, jede mögliche Exceptionform zu behandeln. Aus diesem Grund gibt es bei .NET das try-finally-Konstrukt, was die Auseinandersetzung mit den verschiedenen Exceptions vermeiden helfe und somit der Übersichtlichkeit zugute komme. Sagt jedenfalls Anders Hejlsberg. Ich persönlich halte das für eine komplett falsche Strategie: Ich kann doch nicht schlicht die Fehler ignorieren, statt auf sie zu reagieren! Und selbst wenn meine Reaktion im Ignorieren bestünde, hätte ich mich wenigstens beim Schreiben des Codes damit auseinander gesetzt! Beim try-finally muß ich selbst das nicht (mehr) tun.

Zusammenfassend bleibt festzuhalten, das ich vom Verzicht auf die Checked Exceptions in .NET absolut nicht überzeugt bin. Je mehr Projekte ich mit .NET umsetze, um so mehr stört mich das. Ich meine, natürlich mache ich es so, wie man es sollte:

  • Eigene Exception-Typen definieren
  • Exceptions werfen und das auch im Kommentar anzeigen
  • Exceptions so gezielt und so nah wie möglich abfangen

Alles keine Frage, geschieht auch so. Aber irgendwie ist es – durch die Verwendung von Kommentaren und den Verzicht auf einen dedizierten Kompilerfehler – eben eine reine Kann-Geschichte. Damit sorge ich für Unsicherheiten und für unnötige Fehler, und genau das sollte ja eigentlich vermieden werden.

1 Kommentar » | .NET, Handwerk, Java, Meinung