Arbeiten mit .NET: Das Framework/ Laufzeit/ Klassenbibliotheken/ Bibliothekenbau

Was ist eine Bibliothek?

Bearbeiten

Was eigentlich ist eine Bibliothek? Nun, im richtigen Leben üblicherweise ein Ort, an dem viele Bücher vorhanden sind, aus denen man sich als Leser Informationen holen kann. Die Bücher stehen dort fix und fertig aufgereiht und warten auf ihre Leser.

Dies ist durchaus vergleichbar mit einer Bibliothek im Kontext der Programmierung. Dort ist sie eine Ansammlung von Deklarationen (Datentypen und Code), die zur Verwendung bereitgestellt werden, und zwar, ohne dass diese erneut deklariert und übersetzt werden müssen. Daher ist es klug, häufig benötigte (wiederverendete) Deklarationen von Klassen, Daten(strukturen) und Methoden nicht jeweils erneut in einem konkreten Programm zu notieren, sondern diese in eine Bibliothek auszulagern. Auf die entsprechenden Klassen und deren Daten sowie Methoden kann dann unter Bezugnahme auf die Bibliothek aus anderen Programmen zugegriffen werden. Ein weiterer wichtiger Vorteil dieser Vorgehensweise ist, dass bei einer evtl. notwendigen Änderung oder Verbesserung an einer in einer Bibliothek befindlichen Klasse diese nur an einer Stelle - nämlich in der Bibliothek bzw. deren Quellcode - vorgenommen werden muss.

Allgemein gibt es dabei verschiedene Strategien, um Code in unterschiedlichen Programmen wiederzuverwenden. Viele Programmiersprachen kennen das Inkludieren mittels sogenannter Include-Dateien. In diesen Dateien befindet sich normaler Quelltext, auf den an geeigneter Stelle im Quellcode des Programms via include verwiesen werden kann. Die Folge davon ist, dass der Übersetzer oder Interpreter des Programms einfach die nächsten Zeilen des Quellcodes aus der Include-Datei liest und anschließend nach Abarbeitung der Include-Datei mit der Übersetzung des Programms fortfährt. Solche Bibliothekskonzepte gibt es in vielen Programmiersprachen wie bspw. PHP oder auch in Pascal. Der Nachteil ist, dass bei jeder Übersetzung des einbindenden Programms auch die Include-Datei erneut übersetzt wird. Außerdem kann jeder Benutzer der Include-Datei - da sie ihm ja im Quelltext vorliegen muss - Einblick in die Implementierung nehmen.

Demgegenüber gibt es Bibliothekskonzepte, die aus dem ausgelagerten Code eine eigenständige Bibliothek in compilierter Form erstellen - i.d.R. in Form einer Dynamic Link Library (DLL). Dieser Code kann nun unter Bezugnahme auf den Namespace der Bibliothek eingebunden und die darin befindlichen Klassen mit ihren Methoden und Daten genutzt werden. Natürlich muss ein potentieller Nutzer einer solchen compilierten Bibliothek die Namen der darin befindlichen Klassen, Methoden und Daten kennen. Diese werden dazu oft in Form einer Hilfedatei zusätzlich zu der DLL bereit gestellt.

Anhand eines kleinen Beispiels soll nachfolgend das Erstellen, Erzeugen und Verwenden einer solchen DLL aufgezeigt werden. Dabei geht es um das Bauprinzip einer solchen Bibliothek, weshalb hier ausschließlich einfache Klassenmethoden zum Einsatz kommen sollen.

Beispiel einer Bibliothek

Bearbeiten

Gehen wir zunächst von einer sehr einfachen Konsolanwendung, die zwei einfache Methoden enthält, aus. In der Anwendung geht es darum, eine Integerzahl einzulesen, um anschließend festzustellen, ob diese Zahl gerade oder ungerade ist.

Im ersten Entwurf sind alle für das Problem benötigten Methoden als Klassenmethode in einer gemeinsamen Klasse enthalten. Der Quelltext dazu sieht wie folgt aus:

 
using System;

namespace bibliothek01
{
    class Program
    {
        public static int readInt(string query)
        {
            Console.Write(query + ": ");
            return Convert.ToInt32(Console.ReadLine());
        }

        public static bool isEven(int x)
        {
            return x % 2 == 0;
        }
        
        static void Main(string[] args)
        {
            int z = readInt("Give me an Integer");
            Console.WriteLine("is " + z + " even? " +
                isEven(z));
        }
    }
}

Gespeichert werden soll das Projekt in diesem Beispiel unter dem Namen bibliothek01. Nach Speicherung wird das Programm fehlerfrei ausgeführt.

Nach der Freude über das gelungene Programm fällt uns vielleicht auf, dass wir die darin enthaltenen Lösungen (Einlesen einer Integerzahl, Prüfung, ob eine Zahl gerade oder ungerade ist) ggfs. auch gut in anderen Programmen einsetzen könnten. Das könnte natürlich durch Copy & Paste in das neue Programm erfolgen, dies entspräche zwar dem Ziel der Wiederverwendung, nicht aber dem Ziel, Wartungsarbeiten künftig nur an einer Stelle vornehmen zu wollen. Daher sollen beide Methoden in eine Bibliothek in Form einer DLL ausgelagert werden.

Dazu sind die folgenden Schritte erforderlich:

  1. Erstellung einer Klassenbibliothek
  2. Änderung des obigen Programms (Bezugnahme auf die Bibliothek, Entfernung der in die Bibliothek ausgelagerten Methoden)

Erstellung der Klassenbibliothek

Bearbeiten

Zur Erstellung einer neuen Klassenbibliothek bietet Visual Studio (VS) eine entsprechende Vorlage Klassenbibliothek bei neuen Projekten an. Die neue Klassenbibliothek kann in einem beliebigen Verzeichnis gespeichert werden. Folgende Vorgehensweise bietet sich dazu an: Ausgehend von der Projektmappe, das obiges Projekt enthält, wird ein neues Projekt in Form einer Klassenbibliothek mit dem Namen myTools hinzugefügt. Automatisch wird dabei eine Klasse class1 in der Datei class1.cs erstellt. Diese kann im Projektmappenexplorer mit einem aussagekräftigeren Namen z.B. in Helpers.cs versehen werden (Kontextmenü : Umbenennen).

Im nächsten Schritt können nun einfach die beiden Methoden aus obiger Klasse in die Datei Helpers.cs verschoben werden (Ausschneiden : Einfügen), so dass nun der folgende Quelltext in Helpers.cs entsteht:

 
using System;

namespace myTools
{
    public class Helpers
    {
        public static int readInt(string query)
        {
            Console.Write(query + ": ");
            return Convert.ToInt32(Console.ReadLine());
        }

        public static bool isEven(int x)
        {
            return x % 2 == 0;
        }
    }
}

Änderung des bisherigen Programms

Bearbeiten

Übrig bleibt der Quelltext von Program.cs, der jetzt wie folgt aussieht:

 
using System;

namespace bibliothek01
{
    class Program
    {
        static void Main(string[] args)
        {
            int z = readInt("Give me an Integer");
            Console.WriteLine("is " + z + " even? " +
                isEven(z));
        }
    }
}

Letzterer lässt sich nun allerdings nicht mehr übersetzen, da keinerlei Bezug auf die Klassenmethoden readInt und isEven existieren. Dies liegt zunächst daran, dass Programm und Bibliothek in unterschiedlichen Namespaces angeordnet sind. Um dem Projekt den Zugriff auf die myTools.dll und darin enthaltene Klasse Helpers mit den beiden Methoden zu ermöglichen, sind

  1. dem Projekt bibliothek01 im Kontextmenü des Projektes ein Verweis auf die myTools.dll hinzuzufügen (Kontextmenü bibliothek01 : Verweis hinzufügen : Durchsuchen : Verzeichnis myTools\bin\Debug\myTools.dll) und
  2. die Methodenaufrufe readInt und isEven voll zu qualifizieren.

Letzteres geschieht durch Angabe von myTools.Helpers.readInt("Give me an Integer") und myTools.Helpers.isEven(z). Bereits nach Eingabe von myTools. zeigt zugleich IntelliSense die Klasse Helpers. und nach Übernahme dieser Klasse die öffentlichen Methoden isEven und readInt an, so dass diese bequem selektiert werden können:

 
using System;

namespace bibliothek01
{
    class Program
    {
        static void Main(string[] args)
        {
            int z = myTools.Helpers.readInt("Give me an Integer");
            Console.WriteLine("is " + z + " even? " +
                myTools.Helpers.isEven(z));
        }
    }
}

Alternativ kann die Einbindung des Namespace myTools auch über eine using-Direktive am Anfang des Projektquelltextes erfolgen, wodurch myTools vor Helpers entfallen kann:

 
using System;
using myTools;

namespace bibliothek01
{
    class Program
    {
        static void Main(string[] args)
        {
            int z = Helpers.readInt("Give me an Integer");
            Console.WriteLine("is " + z + " even? " +
                Helpers.isEven(z));
        }
    }
}

Resümee

Bearbeiten

Selbstentwickelte Klassen, die häufig in unterschiedlichen Programmen verwendet werden sollen, werden sinnvollerweise in eigenständige Bibliotheken ausgegliedert. Die Biliotheken lassen sich dann in beliebig vielen anderen Projekten verwenden, indem sie einfach per Verweis diesen Projekten hinzugefügt werden.

Wird eine Änderung im Code der ausgelagerten Klassen notwendig, beschränkt sich diese auf eine Quelldatei. Die Änderungen werden automatisch nach Neuübersetzung der nutzenden Programme wirksam. So könnte beispielsweise die Methode readInt() um eine Fehlerbehandlung bei Eingabe nicht zulässiger Zeichen ergänzt werden:

 
using System;

namespace myTools
{
    public class Helpers
    {
        public static int readInt(string query)
        {
            bool ok = false;
            int result = 0;

            do
            {
                Console.Write(query + ": ");
                try
                {
                    result = Convert.ToInt32(Console.ReadLine());
                    ok = true;
                }
                catch
                {
                     Console.WriteLine("This is not a number! - Please retry...");
                }
            } while(!ok);

            return result;
        }

        public static bool isEven(int x)
        {
            return x % 2 == 0;
        }
    }
}

Nach Neuübersetzung der Bibliothek myTools und des Programms zeigt letzteres das geänderte Verhalten: Die Eingabe nicht numerischer Zeichen wird mit einem Fehler quittiert und führt unmittelbar zur Neueingabe eines Ganzzahlwerts. Gleichermaßen würde sich dieses Verhalten nun bei allen Programmen, die die Bibliothek myTools verwenden, nach Neuübersetzung einstellen.


Ein weiteres Beispiel

Bearbeiten

Eine einfache Funktion einer Dll soll aufrufbar sein aus verschiedenen Programmiersprachen.
Quelltext für "DllAufruf.cs":

 

using System;
using System.Collections.Generic;
using System.Text;

namespace DllAufruf
{

    public class Einfache
    {
        int iTest = 5;
        public int MalFuenf(int i1)
        {
            return i1*iTest;
        }
    }
}

Es wird eine Funktion "MalFuenf" exportiert, die einen Integer-Wert entgegen nimmt und mit 5 multipliziert. Das Ergebnis wird als Integer zurückgegeben.


Quelltext für "TestConsole.cs"! Im VisualStudio wird DllAufruf als Verweis eingebunden!

 

using System;
using System.Collections.Generic;
using System.Text;
using DllAufruf;

namespace TestKonsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello Dll");

            // Deklaration des Objektes i
            Einfache i = new Einfache();

            Console.WriteLine(i.MalFuenf(3));
        }
    }
}

Aufrufendes Programm mit Ausgabe auf der Kommandozeile:

Hello Dll
15