Aus Linux-Magazin 03/2009

C#-Entwicklung unter Linux - Teil 4

© Wojciech Gajda, Fotolia.de

Der vierte Teil des Mono-Workshops beschließt die Serie über die C#-Programmierung unter Linux – und erläutert, wie sich Mono in das Puzzle aus externen Libraries und Datenbanken einfügt.

Die vorausgegangen drei Teile des Mono-Tutorials haben sich bereits zu fortgeschrittenen Themen wie dem Serialisieren von Datenstrukturen, der Kommunikation mit Webservices oder dem Parsen von XML-Dateien vorgewagt. Jedoch kamen dabei für Standardprobleme handgestrickte Lösung zum Einsatz. Von einer effizienten Programmierumgebung erwarten Entwickler aber, für immer wiederkehrende Aufgaben fertige, frei verfügbare Module vorzufinden.

Der abschließende Artikel des C#-Tutorials stellt deshalb exemplarisch weitere Teile der C#-Bibliothek vor. Er bespricht zunächst verschiedene Containerklassen, die Brot-und-Butter-Komponenten jedes Programmierers. Zum Standardrepertoire gehören auch Datenbankzugriffe – ob mit den C#-Bordmitteln oder per Language Integrated Query -, die bereits über die eigentliche Sprache C# hinausgehen. Dies gilt auch für den Aufruf externer nativer Bibliotheken mit »P/Invoke« für den Fall, dass die C#/Mono-Bibliothek keine passende Lösung enthält.

Einige weitere wichtige Techniken wie die GUI-Programmierung mit den seit Version 2.0 von Mono gut unterstützten Windows Forms oder alternativ mit GTK# bleiben in diesem Tutorial außen vor – die Vielfalt von Mono und der verfügbaren Bibliotheken erzwingen eine Beschränkung.

Rasterfahndung

Jede Programmiersprache hat ihr eigenes Konzept zum Speichern von Datensammlungen. In den von C abgeleiteten Sprachen hat sich eine Reihe von Standard-Containerklassen etabliert, die in ähnlicher Form in C++, Java und auch in C# auftauchen.

Mit ihnen verbunden sind Algorithmen zur Suche und zur Manipulation des Containerinhalts. Die Containerklassen sind zudem meist generisch, also auf beliebige Typen anwendbar. Alle in diesem Abschnitt behandelten Containerklassen sind in C# im Namespace »System.Collections.Generic« zu finden.

Der wohl wichtigste Containertyp kam schon in Teil 2 des Tutorials vor: »List<T>«. Das T ist ein Platzhalter für beinahe jeden in C# verfügbaren Typ. Ein »List<int>« dient also zum Beispiel zum Speichern von Integer-Werten. Eine »SortedList<S,T>« speichert dagegen Schlüssel-Wert-Paare und sortiert sie nach dem Schlüssel. Die Hilfsklasse »KeyValuePair<S,T>« ermöglicht den Zugriff auf die Elemente. Listing 1 zeigt ihre Verwendung. Die Zeilen 3 bis 6 erzeugen und füllen eine »SortedList<int,int>« mit von den Schlüsseln her betrachtet unsortierten Schlüssel-Wert-Paaren, die Schleife ab Zeile 8 gibt das Ergebnis aus (Abbildung 1).

Ein »SortedDictionary« bietet dem Nutzer ähnliche Funktionalität wie eine »SortedList« – im Code von Listing 1 lassen sich die beiden Klassen einfach austauschen. Laut Microsofts Beschreibung [1] benötigt jedoch die »SortedList« weniger Speicher. Dafür ist beim »SortedDictionary« für unsortierte Daten das Einfügen und Entfernen schneller. Auch eine Klasse »Dictionary<S,T>« existiert. Wie der Name suggeriert, bleiben die Daten hier unsortiert. Ein »Stack<T>« ist ein Last-in-first-out-Container variabler Länge, eine Queue<T> repräsentiert dagegen das Fifo-Prinzip. Mit »LinkedList<T>« gibt es auch noch eine doppelt verkettete Liste, die beim Einfügen und Entfernen von Werten Vorteile bietet und gut für den sequenziellen, aber schlechter für den wahlfreien Zugriff funktioniert.

Listing 1: Sortierte
Liste

01 class Hauptprogramm {
02   public static void Main(string[] args) {
03     SortedList&lt;int,int&gt; sl = new SortedList&lt;int,int&gt;();
04     sl.Add(3,1);
05     sl.Add(1,2);
06     sl.Add(2,3);
07 
08     foreach(KeyValuePair&lt;int,int&gt; kvp in sl) {
09       Console.WriteLine("KVP Nr. {0} hat Wert {1}",  kvp.Key, kvp.Value);
10     }
11   }
12 }
Abbildung 1: Die Containerklasse »SortedList« kümmert sich automatisch um die Sortierung der Einträge nach Schlüssel.

Abbildung 1: Die Containerklasse »SortedList« kümmert sich automatisch um die Sortierung der Einträge nach Schlüssel.

Daten im Griff

Wer Daten persistent speichert, verwendet dazu häufig externe Datenbanken wie MySQL. Natürlich ist ein Zugriff darauf auch unter Mono/C# möglich. Der passende Connector ist bei [2] unter der GNU General Public License in der Version 2 verfügbar.

Im Test funktionierte das Windows-Binary aus dem Archiv »mysql-connector-net-5.2.5-noinstall.zip« problemlos: C# ist von der Anlage her plattformunabhängig. Bei kritischen Anwendungen ist es aber dennoch angebracht, dies im Einzelfall zu überprüfen. Nach dem Herunterladen muss der Entwickler die DLL noch dem Assemblycache bekannt machen:

# gacutil -i MySql.Data.dll
Installed MySql.Data.dll into the gac (/usr/lib/mono/gac) 

Das einfache Beispielprogramm für den Zugriff auf eine MySQL-Datenbank aus Listing 2 lässt sich aus dem Verzeichnis heraus, in dem die Datei »MySql.Data.dll« liegt, mit folgendem Befehl übersetzten:

gmcs Pfad/mysql.cs-r:System.Data.dll -r: ./MySql.Data.dll 

Listing 2 erwartet, dass die benutzte MySQL-Datenbank bereits existiert.

Listing 2: MySQL in
C#

01 using System;
02 using System.Data;
03 using MySql.Data.MySqlClient;
04
05
06 public class Hauptprogramm
07 {
08   public static void Main(string[] args)
09   {
10     IDbConnection dbcon = new MySqlConnection("Server=localhost;" +
11                                               "Database=test;" +
12                                               "User ID=testuser;" +
13                                         Password=testuserpassword;");
14     dbcon.Open();
15 
16     IDbCommand dbcmd = dbcon.CreateCommand();
17     dbcmd.CommandText = "select vorname, nachname from angestellter";
18 
19     IDataReader reader = dbcmd.ExecuteReader();
20     while(reader.Read()) {
21       string vorname = (string) reader["vorname"];
22       string nachname = (string) reader["nachname"];
23       Console.WriteLine(vorname + " " + nachname);
24     }
25 
26     reader.Close();
27     dbcmd.Dispose();
28     dbcon.Close();
29   }
30 }

Richtig verbunden

Das Programm aus Listing 2 ist weitestgehend selbsterklärend: Es stellt zunächst eine Verbindung zu einer existierenden Datenbank her. Dazu sind grundlegende Daten wie Host des Datenbankservers, Name und Passwort eines lokalen Nutzers sowie der Name der Datenbank erforderlich. Auf den Code zum Öffnen einer Verbindung (Zeilen 9 bis 13) folgt ein SQL-Befehl für das Auslesen eines Datensatzes (Zeile 17). Im Beispiel sucht er nach den Vor- und Nachnamen von Angestellten.

Eine Testdatenbank erzeugt der Datenbanknutzer »root« nach Aufruf von »mysql -u root -p« in der MySQL-Shell:

create database if not exists test;
grant all privileges on test.* to 'testuser'@'localhost' identified by  'testuserpassword'; 

Die Tabellen legt nach dem Neustart der MySQL-Shell ein nicht privilegierter Benutzer mit »mysql -u testuser -p« an:

create table angestellter(vorname varchar(32), nachname varchar(32)); 

Die Tabelle »angestellter«, lässt sich nun mit Vor- und Nachnamen füllen:

insert into angestellter (vorname, nachname) values("Haenschen", "Klein"); 
insert into angestellter (vorname, nachname) values("Gretel", "Klein"); 
insert into angestellter (vorname, nachname) values("boese", "Hexe"); 

Der Aufruf des Programms aus Listing 2 liest dann die Tabelle aus. Eine passende Reader-Klasse (Zeile 19) iteriert nun über alle gefundenen Datensätze und gibt sie nacheinander aus:

mono mysql.exe
Haenschen Klein
Gretel Klein
boese Hexe

Als Aufräumarbeit folgt zum Schluss das Schließen der Datenbankverbindung (Zeilen 26 bis 28).

Mit Links

Das Akronym LINQ steht für Language Integrated Query. LINQ vereinheitlicht Abfragen unterschiedlichster Datenquellen unter einem gemeinsamen API. Dazu gehören auch die gerade vorgestellten Datencontainer. Der Zugriff erfolgt mit einer SQL-ähnlichen Syntax. So wählt Listing 3 aus »zufallsZahlen« vom Typ »List<double>« Werte größer »5.5« aus (»where-Klausel«, Zeile 13).

Neben der speziellen Syntax des LINQ-Statements fällt in diesem Listing eine weitere Besonderheit auf: die implizit typisierten Variablen. Den Typ von »highnumbers« legt erst der Compiler fest. Dies trägt weiter dazu bei, die Syntax der Abfrage verschiedener Datenquellen zu vereinheitlichen.

Work in Progress

Unter die “unterschiedlichen Datenquellen” (Abbildung 2), die sich über LINQ ansprechen lassen, fallen natürlich auch Datenbanken. Unter Mono/C# gilt dies aber erst mit Einschränkungen. Arbeiten zur Erweiterung der Backends auf Datenbanken – darunter zuerst für MySQL – laufen bei Mono erst. Der aktuelle Status lässt sich unter anderem unter [3] erfragen.

Abbildung 2: In der Microsoft-Welt gibt es viele Third-Party-Provider für LINQ-Anbindungen. Mono holt erst langsam auf. Einige Datenbank-Backends gibt es jedoch bereits.

Abbildung 2: In der Microsoft-Welt gibt es viele Third-Party-Provider für LINQ-Anbindungen. Mono holt erst langsam auf. Einige Datenbank-Backends gibt es jedoch bereits.

Implizit und von Anwendern von LINQ-Expressions in der Regel unbemerkt, benutzt Listing 3 übrigens eine weitere jüngst hinzugekommen Neuerung von C#, die Lambda-Ausdrücke. Dabei handelt es sich um anonyme Funktionen, die der Entwickler erst an der Aufrufstelle definiert.

Listing 3: LINQ

01 using System;
02 using System.Collections.Generic;
03 using System.Linq;
04 
05 class Hauptprogramm {
06   public static void Main(string[] args) {
07     List&lt;double&gt; zufallsZahlen = new List&lt;double&gt;();
08     Random rg = new Random();
09     for(int i=0; i&lt;50; i++) zufallsZahlen.Add
(6.0*rg.NextDouble());
10 
11     var highNumbers =
12       from z in zufallsZahlen
13       where z&gt;5.5
14       select z;
15 
16     foreach(var x in highNumbers) Console.WriteLine(x);
17   }
18 }

Hilfe von außen

Obgleich Mono über eine reichhaltige Ausstattung an Bibliotheken verfügt, gibt es Situationen, in denen bestimmte Funktionen fehlen oder die Performance einer reinen C#-Umsetzung nicht ausreicht. Hier hilft der Aufruf externer Bibliotheken, oft aus dem C-Umfeld, mit P/Invoke. Listing 4 zeigt das prinzipielle Vorgehen in Anlehnung an [4].

Listing 4: Libc
einbinden

01 using System;
02 using System.Runtime.InteropServices;
03 
04 class Hauptprogramm {
05   public static void Main(string[] args) {
06     int id = getpid();
07     Console.WriteLine("Die ID des Prozesses ist {0}", id);
08   }
09 
10   [DllImport ("libc.so.6")]
11   private static extern int getpid ();
12 }

Der Beispielcode liest die ID seines eigenen Prozesses mit Hilfe der Libc-Funktion »getpid()« aus. Wie zu sehen ist, reicht es aus, die Bibliothek samt der daraus zu ladenden Funktion anzugeben. Die Laufzeitumgebung erledigt dann den Rest, sie findet und lädt die Bibliothek automatisch. Allerdings entsteht beim Aufruf externer Bibliotheken selten portabler Code – schon die unterschiedlichen Namen von Bibliotheken unter Windows und Linux erschweren dies. C++-Code lässt sich ebenfalls nutzen, der Programmierer sollte ihn jedoch als »extern C« deklarieren.

Auch in der Übergabe von Parametern versteckt sich mancher Fallstrick. Zudem unterliegt externer Code natürlich nicht den Kontroll- und Aufräummechanismen, die C# im Vergleich zu C und C++ zu einer sicheren Sprache machen. Wer externen Code einbindet, sollte also genügend Zeit für Entwicklungs- und Testphasen einplanen. Wegen der besseren Performance von C- und C++-Code kann sich dies aber im Einzelfall durchaus auszahlen.

Das bessere Java

Der vierteilige Mono-Workshop ab Linux-Magazin 11/08 hat das Rüstzeug vermittelt, eigene Ausflüge in die C#- und Mono-Welt zu unternehmen – ein lohnendes Vorhaben, denn in vielen Aspekten erweist sich C# nach Meinung des Autors tatsächlich als “das bessere Java”. Natürlich heißt dies nicht, dass es das performantere C++ je verdrängen wird. Jedoch bietet C# zusammen mit der in Mono integrierten Standardbibliothek nun auch unter Linux eine so große Vielfalt an Funktionalität und Ausdrucksmöglichkeiten, dass sich C# als interessante Alternative für Open-Source-Projekte anbietet. (pkr)

Infos

[1] Sorted List: [http://msdn.microsoft.com/en-us/library/ms132319.aspx]

[2] C#-MySQL-Connector: [http://dev.mysql.com/downloads/connector/net/5.2.html]

[3] LINQ-Datenbankanbindung: [http://code.google.com/p/dblinq2007/]

[4] Aufruf externer Bibliotheken: [http://www.mono-project.com/Interop_with_Native_Libraries]

Der Autor:

Dr. Rüdiger Berlich arbeitet seit 1992 mit Linux und Open Source. Er bereitet am Karlsruhe Institute of Technology einen Open-Source-Spinoff aus dem Bereich der verteilten Optimierung vor.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 3 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben