Interface

Aus Das Sopra Wiki
Version vom 27. September 2012, 16:07 Uhr von Justus (Diskussion | Beiträge) (IDisposable)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Interfaces, oder "Schnittstellen", sind im Prinzip eine Sonderform einer Klasse. Der wichtigste Unterschied jedoch ist, dass alle Member eines Interfaces implizit öffentlich (public) sind. Außerdem besitzt keines der Member eine Implementierung.

Benutzung

Wenn eine Klasse von einem Interface abgeleitet wird, sagt man, die Klasse implementiert das Interface. In diesem Fall müssen ausnahmslos alle Member des Interfaces in der abgeleiteten Klasse überschrieben werden. Interfaces sind also ähnlich zu abstrakten Klassen, mit dem Unterschied, dass Interfaces nur die Signaturen ihrer Member beinhalten. Der größte Unterschied zu abstrakten Klassen besteht in C# jedoch darin, dass Klassen beliebig viele Interfaces implementieren (Mehrfachvererbung), jedoch jeweils nur von einer Klasse erben können.

Interfaces dienen dazu, unterschiedliche Klassen, die verschiedene Basisklassen besitzen, die gleiche Funktionalität zur Verfügung zu stellen.

Deklaration

Die Deklaration eines Interfaces geschieht ähnlich zu der einer Klasse. Konventionsbedingt beginnen in C# alle Namen von Interfaces mit einem großen I. Häufig genutzte Interfaces sind zum Beispiel IEnumerable und IComparer.

Bestandteile einer Interfacedeklaration können sein:

  • Methoden,
  • Eigenschaften und
  • Indexer.

Das folgende Beispiel zeigt die Deklaration eines Interfaces zur Festlegung von Koordinaten in einem zweidimensionalen Koordinatensystem:

public interface ICoordinates
{
   // Properties
   int X
   {
      get;
      set;
   }

   int Y
   {
      get;
      set;
   }

   // Methoden
   double GetDistance(ICoordinates reference);
   void SetToZero()
}

Die implementierende Klasse dieses Interfaces ist damit dazu gezwungen, die Get- und Set-Methoden der Properties zu implementieren.

Eine implementierung könnte zum Beispiel so aussehen:

class CoordinatePoint : ICoordinates
{
   private int x, y;

   #region ICoordinates Members

   public int X
   {
      get { return this.x; }
      set { this.x = value; }
   }

   public int Y
   {
      get { return this.y; }
      set { this.y = value; }
   }

   public double GetDistance(ICoordinates reference)
   {
      return Math.Sqrt(Math.Pow(this.X - reference.X, 2) + Math.Pow(this.Y - reference.Y, 2));
   }

   public void SetToZero()
   {
      this.X = 0;
      this.Y = 0;
   }

   #endregion
}

An diesem Beispiel wird deutlich, dass auch Interfaces - wie Klassen auch - als Typen behandelt werden. Es sind jedoch nur die Member des Interfaces des übergebenen Typs sichtbar. Der Vorteil hiervon ist, dass alle Klassen, die das Interface ICoordinates implementieren, an GetDistance(ICoordinates reference) übergeben werden können, völlig egal, wie die Klassen selbst aussehen.

Besonderheiten

Interfaces können auch andere Interfaces implementieren.

Wird von einer Klasse, die Interfaces implementiert, abgeleitet, so erbt die abgeleitete Klasse nicht nur alle Variablen der Basisklasse, sondern auch alle Member des in der Basisklasse implementierten Interfaces. Die abgeleitete Klasse implementiert außerdem ebenfalls das von der Basisklasse implementierte Interface.

Ein Problem, welches auftauchen könnte ist, dass die Member eines Interfaces gleich heißen, wie die in der Member eines anderen Interfaces und diese in derselben Klasse implementiert werden. In diesem Fall erlaubt C# eine explizite Benennung der entsprechenden Member:

public interface IInterface
{
   int CompareTo(object obj);
}

class ComparedClass : IInterface, IComparable
{
   #region IComparable Members

   int IComparable.CompareTo(object obj)
   {
      // Do something with member IComparable.CompareTo
   }

   #endregion

   #region IInterface Members

   int IInterface.CompareTo(object obj)
   {
      // Do something with member IInterface.CompareTo
   }

   #endregion
}

Im Falle einer expliziten Interfaceimplementierung ist das Schlüsselwort public bei Members des Interfaces nicht erlaubt.

IDisposable

Das Interface IDisposable soll an dieser Stelle gesondert erwähnt werden. Diese Schnittstelle dient der Speicherbereinigung des Garbage Collectors.

Gewöhnlicherweise wird die Garbage-Collection automatisch aufgerufen, wenn sich der Rechner im Leerlauf befindet, oder der Speicher knapp wird. Nicht mehr benötigte Objekte werden dann aus dem Speicher entfernt. Verwendet man jedoch große Objekte, kann es nützlich sein, die Speicherbereinigung explizit auszuführen. Der Destruktor einer Klasse kann nicht explizit aufgerufen werden. Daher muss dies über eine Methode in der betroffenen Klasse geschehen. Das Interface IDisposable dient in C# als Vereinheitlichung hierfür, indem es eben diese Methode, Dispose(), zur Verfügung stellt. Durch Aufruf der Methode des Interfaces wird die Speicherbereinigung durch die Garbage Collection erzwungen.

Wird IDisposable in einer Klasse implementiert, muss auf die folgenden Dinge geachtet werden:

  • Wenn Dispose() mehrfach aufgerufen wird, darf es nicht zu einem Fehler kommen.
  • Ein Aufruf von Dispose() impliziert, dass die Garbage Collection den Destruktor der Klasse nicht mehr aufrufen muss. Daher soll dies der Garbage Collection auch innerhalb von Dispose() mitgeteilt werden.
  • Eine eventuell vorhandene Dispose()-Methode der Basisklasse sollte ebenfalls aufgerufen werden.
  • Wenn Dispose() nicht explizit aufgerufen wird, muss der Destruktor die Aufgabe des Aufräumens übernehmen (indem er Dispose() aufruft).

In der Regel sollten zwei Versionen von Dispose() implementiert werden. Eine, die keine Parameter entgegen nimmt und damit aus dem Interface direkt stammt. Die andere, die einen booleschen Parameter, disposing entgegen nimmt. Der Sinn dieser zweiten Methode ist es, eine Unterscheidung zwischen einem expliziten Benutzeraufruf und dem Aufruf über den Destruktor bzw. über die Garbage Collection zu treffen. Bei explizitem Aufruf ist es erlaubt, sowohl von der Garbage Collection verwaltete, als auch nicht verwaltete Ressourcen zu bereinigen. Der implizite Aufruf über die Garbage Collection selbst erlaubt dies nicht. Verwaltete Ressourcen dürfen dann nicht entfernt werden, da diese in einer Queue stehen, welche vom Garbage Collector selbst abgearbeitet wird. Würden diese Ressourcen vorzeitig aus dem Speicher - aber nicht aus der Queue - entfernt werden, würde ein Laufzeitfehler resultieren, da der Garbage Collector versucht, ein nicht mehr existierendes Objekt aus dem Speicher zu entfernen.

Das folgende Codebeispiel soll als eine Art Schablone für die Verwendung des IDisposable-Interfaces dienen:

class MyResource : IDisposable
{
   private bool disposed = false;

   #region IDisposable Members

   public void Dispose()
   {
      Dispose(true);

      // Mit GC.SuppressFinalize(this) wird dem Garbage Collector mitgeteilt, dass er dieses
      // Objekt aus seiner Queue nehmen soll.
      GC.SuppressFinalize(this);
   }

   #endregion

   // Wenn disposing = true ist, wird die Dispose() Methode explizit vom Benutzer aufgerufen.
   // Hier können von diesem Objekt verwendete Objekte ebenfalls disposed werden.
   // Wenn disposing = false ist, wird Dispose() vom Garbage Collector selbst aufgerufen.
   // In diesem Fall sollten die von diesem Objekt gehaltenen Ressourcen nicht eigenständig
   // freigegeben werden, da sich diese auch in der Garbage Collection Queue befinden.
   private void Dispose(bool disposing)
   {
      if (!this.disposed)
      {
         if (disposing)
         {
            // Entferne von diesem Objekt verwendete Ressourcen aus dem Speicher
         }

         this.disposed = true;
      }
   }

   // Der Destruktor wird nur dann aufgerufen, wenn die Dispose()-Methode nicht explizit
   // aufgerufen wird.
   ~MyResource()
   {
      Dispose(false);
   }
}

Zu IDisposable sollte ebenfalls der MSDN-Artikel zu Rate gezogen werden.