Startseite  Index  <<  >>  

8 - Interfaces

8.1 Grundprinzip

Wenn Sie mit Instanzen von Klassen arbeiten, können Sie nur die öffentlichen Methoden und Eigenschaften über Instanzen (Objekte) der Klasse nutzen. Öffentliche Variablen (Felder) sollte es ja eigentlich nicht geben, sondern der Zugriff darauf über Methoden hergestellt werden. Alle public-Member einer Klasse stellen gemeinsam die Schnittstelle einer Klasse dar.

Eine Interface-Deklaration definiert genau eine solche Schnittstelle. Wozu dieser zusätzliche Schritt? Eine Klasse kann mehrere Interfaces implementieren, d.h., Objekte der Klasse können unter verschiedenen Typen (Interfaces sind auch Typen) auftreten. Auf diese Weise können z.B. Objekte von Klassen unterschiedlichster Funktionalität an Methoden als Parameter übergeben werden, wenn dieser Parameter von einem Interface-Typ ist. Da die Klasse das Interface implementiert hat, können nun die Methoden des Interface sicher aufgerufen werden.

Sie können sich Interfaces auch als Adapter vorstellen und eine Klasse ist dann ein Multiadapter. Die Klasse kann nun überall dort eingesetzt werden (bzw. ihre Objekte), wo einer der von der Klasse implementierten Adapter benötigt wird.

Beispiel: Interfaces definieren einen Ausschnitt der öffentlichen Schnittstelle einer Klasse

Eine Anwendung Ihrer Firma verwaltet Kundendaten, die unterschiedlichste Informationen beinhalten. Als Datenbank haben Sie zu Beginn MS Access im Einsatz. Nachdem sich Ihre Firma sehr positiv entwickelt hat und viele Kunden dazu gewonnen wurden, benötigen Sie eine leistungsfähigere Datenbank, z.B. den MS SQL Server. Durch die Verwendung von Interfaces kann auf solche möglichen Änderungen schon während der Anwendungsentwicklung der ersten Version Rücksicht genommen werden. Ein Interface definiert dazu alle Methoden, die unabhängig vom Typ der Datenbank für den Zugriff darauf benötigt werden. Zwei (oder auch mehr) Klassen implementieren dann das Interface für eine konkrete Datenbank - in diesem Fall zuerst einmal für MS Access. Später arbeitet eine Anwendung nicht direkt mit den konkreten Klassen, sondern mit den Interfaces. Auf diese Weise lässt sich einfach die Funktionalität im "Hintergrund" austauschen, ohne dass die Anwendung geändert werden muss (siehe ).

Interfaces sind die Schnittstelle zur konkreten Implementierung

Abbildung 8.1: Interfaces sind die Schnittstelle zur konkreten Implementierung

Ziel eines objektorientierten Entwurfs sollte es also sein, nicht direkt mit den implementierenden Klassen zu arbeiten, sondern mit deren Schnittstelle zu kommunizieren, die über Interfaces definiert wird. Dieser Umstand wird auch mit dem Prinzip beschrieben, dass auf eine Schnittstelle hin programmiert werden soll, nicht auf eine Implementierung (also eine Klasse).

8.2 Interfaces deklarieren

Interfaces (Schnittstellen) werden grundsätzlich wie Klassen deklariert, da Interfaces ebenfalls Typen sind. Ein Interface enthält allerdings selbst keinen ausführbaren Code, sondern kann in C# nur Methodendeklarationen, Eigenschaften, Ereignisse und Indexer enthalten.

Syntax
  • Eine Interface-Deklaration wird über das Schlüsselwort interface eingeleitet.
  • Optional können Sie die Modifizierer public und internal verwenden.
  • Danach folgt der Schnittstellenname, dem standardmäßig der Buchstabe (I) vorangestellt wird und der sich ansonsten an die Vorgaben für Bezeichner halten muss.
  • Eine Schnittstelle kann auch andere Schnittstellen erweitern. Diese werden durch einen Doppelpunkt getrennt, hinter dem Namen, angegeben. Mehrere Schnittstellen werden durch Komma getrennt. Das neue Interface erbt damit alle Methodenrümpfe der vererbten Interfaces.
  • Die Elemente einer Schnittstelle sind automatisch public und abstract, so dass Sie diese Modifizierer weder angeben müssen noch angeben dürfen.
  • Schnittstellen können Deklarationen für Methoden, Eigenschaften, Indexer und Ereignisse beinhalten.
  • Da Schnittstellen keine Implementierungen besitzen, dürfen Sie auch keine Instanzen einer Schnittstelle erzeugen. Schnittstellen besitzen keine Konstruktoren und Destruktoren.
  • Es ist aber möglich, eine Variable vom Typ einer Schnittstelle zu deklarieren. Dieser können Instanzen von Klassen zugewiesen werden, welche die Schnittstelle implementieren (das gleiche Konzept finden Sie bei abstrakten Klassen wieder).
    [public|internal] interface IName[: Interface1, Interface2, ...]
    {
      // Methoden
      // Eigenschaften
      // Indexer
      // Ereignisse
    }

    ACHTUNG

    Oft werden Interfaces als Ersatz für die fehlende Mehrfachvererbung angesehen. Dies ist aber eine irreführende Aussage. Sinn der Mehrfachvererbung ist es ja, etwas von mehreren Klassen zu erben, also auch Funktionalität. Interfaces besitzen aber keine Implementierungen wie z.B. abstrakte Klassen.

    INFO

    Schnittstellen werden Ihnen sehr häufig begegnen. Bekannte Schnittstellen sind z.B. IFormattable, IComparable oder IDisposable. Die Klassen, welche diese Schnittstellen implementieren, stellen dadurch Methoden zur Verfügung, um eine String-Repräsentation des Objekts bereitzustellen, um Objekte zu vergleichen oder um eine gezieltere Freigabe von Ressourcen zu ermöglichen.

    Beispiel: Eine Schnittstelle deklarieren

    Da Sie ein größeres Entwicklerteam leiten, benötigen Sie eine automatisierte Lösung, um den Autor (Programmierer) sowie die aktuelle Version einer C#-Klasse zu bestimmen. Dies ist auch für die Durchführung von Tests nützlich, da Sie bemerkt haben, dass nicht immer die aktuellsten Versionen der Klassen verwendet wurden. Damit die Entwickler eine "Vorlage" zur Bereitstellung dieser Informationen bekommen, deklarieren Sie ein Interface mit zwei Methoden GetAutor() und GetVersion(), welche die jeweilige Information zurückgeben. Neben der "Vorlagenfunktion" hat dies noch den Vorteil, dass Sie mit einer einzigen Methode, die einen Parameter vom Typ der Schnittstelle entgegennimmt, die Informationen zentral auswerten können - vorausgesetzt, Sie haben ein Objekt der betreffenden Klassen zur Verfügung.

    Die Schnittstelle IVersionsInfo enthält nur die Methodendeklaration der beiden Methoden ohne eine Implementierung. Diese müssen später die konkreten Klassen vornehmen.

    public interface IVersionsInfo
    {
      string GetAutor();
      string GetVersion();
    }
    Listing 8.1: EinfacheSchnittstellen.cs

    8.2.2 Interfaces implementieren

    Schnittstellen werden von Klassen implementiert oder durch andere Schnittstellen erweitert (Schnittstellenvererbung). Ein Klasse muss dazu alle Elemente der Schnittstelle implementieren, also mit Leben versehen. Wird eine Methode der Schnittstelle nicht in der Klasse implementiert, entsteht eine abstrakte Klasse. Die Implementierung der Schnittstelle in einer Klasse ist notwendig, da Sie keine Instanzen von einer Schnittstelle bilden können. Es lassen sich aber Variablen vom Typ einer Schnittstelle deklarieren. Diesen können Objekte von Klassen zugewiesen werden, welche die betreffende Schnittstelle implementieren.

    Syntax
  • Um eine Schnittstelle zu implementieren, geben Sie nach dem Klassennamen einen Doppelpunkt, optional eine Basisklasse, das Interface oder eine Liste von Interfaces an.
  • Die Klasse muss dann alle Methoden des Interface implementieren. Diese müssen dann als deklariert werden, da sie als Schnittstelle nach außen fungieren sollen. Diese Form der Implementierung wird auch implizite Schnittstellendeklaration genannt.
  • Wurde eine Methode von einer Klasse geerbt, welche die gleiche Signatur wie die einer Schnittstelle besitzt, muss die Methode nicht mehr implementiert werden, sie ist es ja bereits.
  • Da an die Klassendeklaration keine Einschränkungen gemacht werden, kann die Klasse beliebige Methoden besitzen - Methoden, die in keinem Interface vorkommen, und Methoden, um ein oder mehrere Interfaces zu implementieren.
  • Implementiert eine Klasse nicht alle Methoden eines Interface, wird sie damit automatisch zu einer abstrakten Klasse, muss mit abstract gekennzeichnet werden und es können keine Instanzen der Klasse gebildet werden.
  • Erbt eine Klasse von einer anderen Klasse, erbt sie auch die implementierten Interfaces dieser Klasse.
  • Besitzen zwei Interfaces identische Methodensignaturen, können Sie den Interface-Namen vor der Methode angeben, um sie eindeutig zu kennzeichnen (mehr dazu unter dem Thema "Explizite Interface-Deklaration").
    class Name: [Basisklasse,] IInterfaceName[, IInterfaceName...]
    {
      // implizite Schnittstellendeklaration
      public  SchnittstellenMethode()
      {
        ...
      }
      // explizite Schnittstellendeklaration
      ISchnittstellenName.SchnittstellenMethode()
      {
        ...
      }
    }

    ACHTUNG

    Achten Sie beim Implementieren einer Schnittstelle darauf, dass Sie die Methoden in der implementierenden Klasse mit dem Modifizierer public kennzeichnen. Ansonsten erhalten Sie eine Fehlermeldung des Compilers. In der interface-Deklaration dürfen Sie public nicht angeben, da die Methoden automatisch public sind.

    INFO

    Das Visual Studio unterstützt Sie bei der Implementierung von Schnittstellen über Smarttags. Setzen Sie dazu den Cursor hinter den Schnittstellennamen. Es wird ein kleines Symbol unter dem ersten Buchstaben der Schnittstelle angezeigt. Wenn Sie mit der Maus darüber fahren und dann auf den Pfeil klicken, wird ein Menü geöffnet in dem Sie sich entscheiden können, wie Sie die Schnittstelle implementieren wollen. Die zweite "explizite" Implementierung wird im Anschluss besprochen.

    Damit Sie bei dieser automatisierten Implementierung nicht vergessen, die Methoden mit "vernünftigen" Anweisungen zu versehen, werden darin Exceptions ausgelöst.

    Beispiel: Deklarieren und Implementieren von Schnittstellen

    Die Klasse Mathe implementiert die Schnittstelle IVersionsInfo und muss deshalb die beiden Methoden GetAutor() und GetVersion() deklarieren. Denken Sie daran, dass diese Methoden mit dem Zugriffsmodifizierer public versehen werden müssen. Die statische Methode DruckeVersionsInfo() besitzt einen Parameter vom Typ der Schnittstelle. In der Main()-Methode wird dieser Methode ein Mathe-Objekt übergeben. Da die Klasse Mathe auf jeden Fall die Methoden des Interface implementiert (sonst würde hier schon der Compiler Widerspruch einlegen), können diese nun sicher in der Methode DruckeVersionsInfo() aufgerufen werden. Grundsätzlich können beliebige Objekte mit völlig unterschiedlicher Funktionalität übergeben werden - sicher ist ja, dass sie die Schnittstelle IVersionsInfo implementieren. Über die Methode GetType() wird zusätzlich der "richtige" Typ ausgegeben, der an DruckeVersionsInfo() übergeben wurde.

    Schnittstellen über Smarttags automatisch implementieren

    Abbildung 8.2: Schnittstellen über Smarttags automatisch implementieren

    using System;
    namespace CSharpBuch.Kap08
    {
      public class EinfacheSchnittstellen
      {
        static void Main(string[] args)
        {
          Mathe m = new Mathe();
          DruckeVersionsInfo(m);
          Console.ReadLine();
        }
        private static void DruckeVersionsInfo(IVersionsInfo vi)
        {
          Console.WriteLine(vi.GetType().ToString());
          Console.WriteLine(vi.GetAutor());
          Console.WriteLine(vi.GetVersion());
        }
      }
      public interface IVersionsInfo
      {
        string GetAutor();
        string GetVersion();
      }
      public class Mathe: IVersionsInfo
      {
        private static string version = "0.0.97.123";
        private static string autor = "Dirk Frischalowski";
        public string GetAutor()
        {
          return autor;
        }
        public string GetVersion()
        {
          return version;
        }
      }
    }

    Listing 8.2: EinfacheSchnittstellen.cs

    INFO

    Sie werden es wahrscheinlich nicht immer erreichen, dass eine Schnittstelle genau die Methoden enthält, die von den implementierenden Klassen benötigt werden. Es kann überflüssige Methoden geben, für die es in einigen Klassen nicht sinnvoll ist, sie zu implementieren. Dennoch möchten Sie die Schnittstelle von diesen Klassen implementieren lassen (und nicht mehrere bereitstellen). Eine Lösung, die auch im .NET Framework verwendet wird, löst in den nicht implementierten Methoden eine Exception aus, wie es z.B. auch das Visual Studio bei der automatisierten Implementierung macht. In diesem Fall bemerkt der Entwickler bei deren Aufruf (spätestens der Anwender), dass die Methode nicht zur Verfügung steht, und muss entsprechend darauf reagieren.

    8.3 Explizite Interface-Deklaration

    Bei der Implementierung mehrerer Schnittstellen innerhalb einer Klasse kann der Fall eintreten, dass sich eine Methode mit dem gleichen Namen und der gleichen Signatur in mehreren Interfaces befindet. Um nun jede Methode eines spezifischen Interface separat zu implementieren, setzen Sie den Namen des Interface vor die Methodendeklaration, z.B.

    string IVersionsInfo.GetAutor()
    {
    }

    Weiterhin können Sie auf diese Weise Methoden des Interfaces in der öffentlichen Schnittstelle einer Klasse verbergen, wenn sie nur für interne Aufgaben innerhalb der Klasse benötigt werden. Über die Schnittstelle sind sie aber dennoch verfügbar - dazu gleich ein Beispiel.

    Ein weiteres Problem kann sich ergeben, wenn mehrere Interfaces Methoden mit gleichen Namen, aber unterschiedlichen Parameterlisten beinhalten. Auch in diesem Fall kann die korrekte Methode beim Aufruf nicht ohne Weiteres ausfindig gemacht werden.

    INFO

    Sie können durchaus eine implizite Implementierung durch eine einzige Methode vornehmen, die sich aber in mehreren Interfaces befindet. In diesem Fall wird diese Methode für beide Schnittstellen verwendet.

    Bei der expliziten Deklaration gibt es nun einige Unterschiede zur impliziten Deklaration:

  • Den Methoden werden die Namen ihrer Interfaces vorangestellt.
  • Die Methoden der Interfaces können nicht mehr über ihren Namen aufgerufen werden, sondern nur noch über ein Objekt vom Typ des Interface. Dies ist auch sinnvoll, da der Methodenname allein nicht mehr eindeutig wäre.
  • Methoden können nicht mehr virtuell implementiert werden.
  • Es darf kein Zugriffsmodifizierer bei der Implementierung angegeben werden.
  • Sie können bei der Implementierung der Methoden explizite und implizite Deklarationen mischen. Auf diese Weise lassen sich die Methoden eines Interface (z.B. die Methoden, die mit Sicherheit am häufigsten verwendet werden) implizit und die eines anderen explizit implementieren.

    Beispiel: Explizite Implementierung mehrerer Interfaces

    Die beiden Interfaces IMathe1 und IMathe2 besitzen beide die Methode Add(), während IMathe1 zusätzlich noch die Methode Sub() enthält. Die Klasse Mathe implementiert nun beide Interfaces und nutzt die explizite Interface-Deklaration, um die Methode Add() für jedes Interface separat zu implementieren. Vor den Methoden werden dazu die Interfacenamen angegeben. Sie dürfen in diesem Fall nicht den Modifizierer public angeben. Die Methode Sub() wird implizit deklariert, da sie nur in einem Interface vorkommt.

    Alternativ könnten Sie auch die Methode Add() implizit deklarieren und darin eine der beiden Add()-Methoden der Interfaces aufrufen (((IMathe2)this).Add()). Auch hier muss das Objekt in den Interface-Typ gecastet werden, um die Add()-Methode der Klasse aufzurufen.

    In der Main()-Methode wird nun ein Mathe-Objekt erzeugt und die Methode Add() vom Interface IMathe1 aufgerufen. Dazu ist zwingend ein Cast notwendig (m as IMathe1). Interessant daran ist, dass die Methoden nicht einmal als public deklariert sind. Da Sub() implizit deklariert wurde, kann diese Methode ganz normal über das Objekt aufgerufen werden.

    using System;
    namespace CSharpBuch.Kap08
    {
      public class ExpliziteSchnittstellen
      {
        static void Main(string[] args)
        {
          Mathe m = new Mathe();
          // m.Add(10, 11); => Compilerfehler
          Console.WriteLine((m as IMathe1).Add(10, 11));
          Console.WriteLine(m.Sub(10, 11));
          Console.ReadLine();
        }
      }
      public interface IMathe1
      {
        int Add(int zahl1, int zahl2);
        int Sub(int zahl1, int zahl2);
      }
      public interface IMathe2
      {
        int Add(int zahl1, int zahl2);
      }
      public class Mathe : IMathe1, IMathe2
      {
        public int Sub(int zahl1, int zahl2)
        {
          return zahl1 - zahl2;
        }
        int IMathe1.Add(int zahl1, int zahl2)
        {
          return zahl1 + zahl2;
        }
        int IMathe2.Add(int zahl1, int zahl2)
        {
          return zahl1 + zahl2;
        }
        /*
        public int Add(int zahl1, int zahl2)
        {
          return ((IMathe2)this).Add(zahl1, zahl2);
        }
        */
      }
    }
    Listing 8.3: ExpliziteSchnittstellen.cs

    8.4 Das Interface IDisposable

    Der Garbage Collector ist im .NET Framework für die Freigabe von nicht mehr genutztem Speicher verantwortlich. Allerdings hat er den Nachteil, dass sein Ausführungszeitpunkt und damit der Aufruf des Destruktors nicht vorhersehbar sind. Dies ist unter Umständen nicht gewünscht, da eine Datei eventuell sofort geschlossen oder eine Datenbankverbindung sofort freigegeben werden soll, wenn das Objekt nicht mehr benötigt wird.

    Die Lösung besteht darin, dass eine Klasse eine spezielle Methode mit dem Namen Dispose() zur Verfügung stellt (der Name der Methode ist prinzipiell frei wählbar). Diese Methode wird manuell aufgerufen und nimmt die notwendigen Schritte, d.h. die Aufräumarbeiten vor, wenn das Objekt nicht mehr benötigt wird. Bei der Arbeit mit Dateien oder Datenbanken wird statt Dispose() auch häufig die Methode Close() verwendet. Dies würde nun grundsätzlich reichen. Das .NET Framework stellt mit der Schnittstelle IDisposable noch eine etwas umfangreichere Lösung zur Verfügung.

    Wenn eine Klasse das Interface IDisposable implementiert, verpflichtet sie sich, die Methode Dispose(), welche die einzige Methode des Interface ist, zu implementieren. Sinn des Ganzen ist es, dass nun beispielsweise eine einfache Prüfmöglichkeit besteht, um herauszufinden, ob ein Objekt eine Methode Dispose() besitzt und damit Ressourcen vorzeitig freigegeben werden können.

    if(objektName is IDisposable)
      (objektName as IDisposable).Dispose();

    Wenn Sie direkt mit einem konkreten Objekt arbeiten, wissen Sie natürlich, ob es eine Methode Dispose() besitzt, da dies ja bereits IntelliSense im Visual Studio anbietet. Wird ein Objekt aber an eine Methode übergeben, die einen Parameter vom Typ Object erwartet, ist dies erst einmal nicht gegeben.

    Bei der Implementierung von IDisposable sind nun mehrere Dinge zu beachten:

  • Da die Methode Dispose() manuell aufgerufen wird, müssen Sie sicherstellen, dass der mehrfache (versehentliche) Aufruf erkannt wird und keine negativen Folgen hat.
  • Da nun ein Destruktoraufruf eventuell nicht mehr notwendig ist - in der Methode Dispose() haben Sie schon alles an Aufräumarbeiten erledigt - kann dies dem Garbage Collector mitgeteilt werden. Der GC ruft in diesem Fall den Destruktor des Objekts nicht mehr auf.
  • Rufen Sie in Dispose() eine mögliche Dispose()-Methode der Basisklasse auf.
  • Wenn der Aufruf von Dispose() vergessen wurde bzw. nicht notwendig war, muss der Destruktor die Aufgaben von Dispose() übernehmen. Dies wird normalerweise dadurch gelöst, dass der Destruktor seinerseits Dispose() aufruft, so dass es nur eine Bereinigungsmethode gibt.

    INFO

    Stellen Sie neben einer Dispose()-Methode zur Sicherheit auch immer einen Destruktor zur Verfügung, da der Aufruf von Dispose() ja nicht zwingend erforderlich ist.

    ACHTUNG

    In der Dokumentation zum .NET Framework wird auch oft von einer Finalize()-Methode gesprochen. Diese steht aber nur unter Visual Basic zur Verfügung. In C# entspricht dies dem Destruktor, der aber zum Teil andere Eigenschaften besitzt.

    Beispiel: Vorlage zur Freigabe von Ressourcen über das Dispose-Muster

    Die Klasse DBZugriff, die eine Datenbankverbindung nutzen soll, implementiert zum manuellen Schließen der Datenbankverbindung das Interface IDisposable. Dazu wird die Methode Dispose() bereitgestellt. Diese ruft ihrerseits die Methode Dispose() auf, welche einen booleschen Parameter besitzt. Ziel dieses Umwegs ist es, dass Dispose() auch vom Destruktor aus aufgerufen werden kann, so dass es nur eine Methode gibt, die für die Freigabe von Ressourcen verantwortlich ist. Dies wäre dann die parametrisierte Dispose(bool)-Methode.

    Weiterhin wird in Dispose() die Methode SuppressFinalize() aufgerufen. Dadurch wird der Garbage Collector veranlasst, nicht mehr den Destruktor des im Parameter übergebenen Objekts aufzurufen, da bereits alles bereinigt ist. Dies führt zu einer geringen Steigerung der Verarbeitungsgeschwindigkeit. In der Methode Dispose(bool) wird zuerst geprüft, ob Dispose(bool) bereits aufgerufen wurde. In diesem Fall ist nichts mehr zu tun. Im anderen Fall wird geprüft, ob der Parameter der Methode den Wert true oder false hat. Wird die Methode vom Destruktor aufgerufen, müssen keine Managed Ressourcen freigegeben werden, da dies der Destruktor erledigt. Rufen Sie die Methode aber von Dispose() aus auf, können auch diese Ressourcen gleich freigegeben werden. In jedem Fall werden Dateien und Datenbankverbindungen geschlossen, d.h. Operationen durchgeführt, die nicht in das Aufgabengebiet der Garbage Collection fallen. Damit Dispose() nicht mehrmals ausgeführt wird, wird zum Abschluss die Variable disposeHandled auf true gesetzt.

    Da für Dateien und Datenbanken die Bereitstellung einer Methode Close() intuitiver ist, wird auch diese implementiert. Sie ruft ihrerseits wieder die Methode Dispose() auf.

    Die gesamte hier vorgestellte Implementierung von IDisposable wird im .NET Framework auch unter dem Namen Dispose()-Pattern geführt.

    public class DBZugriff: IDisposable
    {
      private bool disposeHandled = false;
      public DBZugriff()
      {
        Console.WriteLine("Objekt erzeugt...");
      }
      public void Close()
      {
        Dispose();
      }
      public void Dispose()
      {
        Dispose(true);
        GC.SuppressFinalize(this);
      }
      protected virtual void Dispose(bool callDispose)
      {
        if(!disposeHandled)
        {
          Console.WriteLine("Objekt freigegeben...");
          if(callDispose)
          {
            // ... Managed-Objekte freigeben
          }
          // Dateien oder Datenbankverbindung schließen
          disposeHandled = true;
        }
      }
      ~DBZugriff()
      {
        Dispose(false);
        Console.WriteLine("Destruktor von Objekt aufgerufen...");
      }
    }
    Listing 8.4: ManuelleBereinigung.cs

    8.5 Übungsaufgaben

    Aufgabe 1
    Sie möchten mit einer Anwendung Lottoziehungen simulieren. Dazu werden die Ziehungen 5 aus 35 und 6 aus 49 implementiert.

    Definieren Sie eine Schnittstelle, welche die Methoden GetLottoZahl(int maxZahl) und GetAnzahl() enthält. Die erste Methode liefert eine Zufallszahl im Bereich von 1 bis maxZahl. Die zweite Methode liefert die Anzahl der zu ziehenden Zahlen, also 5 oder 6. Diese Methoden sollen unter anderem von den Klassen Lotto5Aus35 und Lotto6Aus49 implementiert werden. Verwenden Sie die die Klassen in einer Testanwendung.

    Zum Erzeugen von Zufallszahlen verwenden Sie die Klasse Random aus dem Namespace System. Die Methode Next(int n) liefert dazu einen int-Wert zwischen 0 und n-1. Random rd = new Random(); int i = rd.Next(35) + 1;

    Aufgabe 2
    Lösen Sie die Aufgabe 1 über die explizite Schnittstellen-Deklaration.
    Aufgabe 3
    Deklarieren Sie eine Klasse DateiLogging, welche Log-Informationen in eine Datei ausgeben soll. Die Dateioperationen werden hier nur durch Ausgaben auf der Konsole simuliert. Im Konstruktor wird dazu die betreffende Datei geöffnet und bis zur Freigabe nicht wieder geschlossen. Implementieren Sie die Schnittstelle IDisposible, um die Datei durch den Aufruf der Methode Dispose() des Objekts sofort zu schließen.